200 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			200 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
This file is part of Telegram Desktop,
 | 
						|
the official desktop application for the Telegram messaging service.
 | 
						|
 | 
						|
For license and copyright information please follow this link:
 | 
						|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
						|
*/
 | 
						|
#include "media/streaming/media_streaming_utility.h"
 | 
						|
 | 
						|
#include "media/streaming/media_streaming_common.h"
 | 
						|
#include "ui/image/image_prepare.h"
 | 
						|
#include "ffmpeg/ffmpeg_utility.h"
 | 
						|
 | 
						|
namespace Media {
 | 
						|
namespace Streaming {
 | 
						|
namespace {
 | 
						|
 | 
						|
constexpr auto kSkipInvalidDataPackets = 10;
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
crl::time FramePosition(const Stream &stream) {
 | 
						|
	const auto pts = !stream.frame
 | 
						|
		? AV_NOPTS_VALUE
 | 
						|
		: (stream.frame->best_effort_timestamp != AV_NOPTS_VALUE)
 | 
						|
		? stream.frame->best_effort_timestamp
 | 
						|
		: (stream.frame->pts != AV_NOPTS_VALUE)
 | 
						|
		? stream.frame->pts
 | 
						|
		: stream.frame->pkt_dts;
 | 
						|
	return FFmpeg::PtsToTime(pts, stream.timeBase);
 | 
						|
}
 | 
						|
 | 
						|
FFmpeg::AvErrorWrap ProcessPacket(Stream &stream, FFmpeg::Packet &&packet) {
 | 
						|
	Expects(stream.codec != nullptr);
 | 
						|
 | 
						|
	auto error = FFmpeg::AvErrorWrap();
 | 
						|
 | 
						|
	const auto native = &packet.fields();
 | 
						|
	const auto guard = gsl::finally([
 | 
						|
		&,
 | 
						|
		size = native->size,
 | 
						|
		data = native->data
 | 
						|
	] {
 | 
						|
		native->size = size;
 | 
						|
		native->data = data;
 | 
						|
		packet = FFmpeg::Packet();
 | 
						|
	});
 | 
						|
 | 
						|
	error = avcodec_send_packet(
 | 
						|
		stream.codec.get(),
 | 
						|
		native->data ? native : nullptr); // Drain on eof.
 | 
						|
	if (error) {
 | 
						|
		LogError(qstr("avcodec_send_packet"), error);
 | 
						|
		if (error.code() == AVERROR_INVALIDDATA
 | 
						|
			// There is a sample voice message where skipping such packet
 | 
						|
			// results in a crash (read_access to nullptr) in swr_convert().
 | 
						|
			&& stream.codec->codec_id != AV_CODEC_ID_OPUS) {
 | 
						|
			if (++stream.invalidDataPackets < kSkipInvalidDataPackets) {
 | 
						|
				return FFmpeg::AvErrorWrap(); // Try to skip a bad packet.
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return error;
 | 
						|
}
 | 
						|
 | 
						|
FFmpeg::AvErrorWrap ReadNextFrame(Stream &stream) {
 | 
						|
	Expects(stream.frame != nullptr);
 | 
						|
 | 
						|
	auto error = FFmpeg::AvErrorWrap();
 | 
						|
 | 
						|
	do {
 | 
						|
		error = avcodec_receive_frame(
 | 
						|
			stream.codec.get(),
 | 
						|
			stream.frame.get());
 | 
						|
		if (!error
 | 
						|
			|| error.code() != AVERROR(EAGAIN)
 | 
						|
			|| stream.queue.empty()) {
 | 
						|
			return error;
 | 
						|
		}
 | 
						|
 | 
						|
		error = ProcessPacket(stream, std::move(stream.queue.front()));
 | 
						|
		stream.queue.pop_front();
 | 
						|
	} while (!error);
 | 
						|
 | 
						|
	return error;
 | 
						|
}
 | 
						|
 | 
						|
bool GoodForRequest(const QImage &image, const FrameRequest &request) {
 | 
						|
	if (request.resize.isEmpty()) {
 | 
						|
		return true;
 | 
						|
	} else if ((request.radius != ImageRoundRadius::None)
 | 
						|
		&& ((request.corners & RectPart::AllCorners) != 0)) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	return (request.resize == request.outer)
 | 
						|
		&& (request.resize == image.size());
 | 
						|
}
 | 
						|
 | 
						|
QImage ConvertFrame(
 | 
						|
		Stream &stream,
 | 
						|
		AVFrame *frame,
 | 
						|
		QSize resize,
 | 
						|
		QImage storage) {
 | 
						|
	Expects(frame != nullptr);
 | 
						|
 | 
						|
	const auto frameSize = QSize(frame->width, frame->height);
 | 
						|
	if (frameSize.isEmpty()) {
 | 
						|
		LOG(("Streaming Error: Bad frame size %1,%2"
 | 
						|
			).arg(frameSize.width()
 | 
						|
			).arg(frameSize.height()));
 | 
						|
		return QImage();
 | 
						|
	} else if (!FFmpeg::FrameHasData(frame)) {
 | 
						|
		LOG(("Streaming Error: Bad frame data."));
 | 
						|
		return QImage();
 | 
						|
	}
 | 
						|
	if (resize.isEmpty()) {
 | 
						|
		resize = frameSize;
 | 
						|
	} else if (FFmpeg::RotationSwapWidthHeight(stream.rotation)) {
 | 
						|
		resize.transpose();
 | 
						|
	}
 | 
						|
 | 
						|
	if (!FFmpeg::GoodStorageForFrame(storage, resize)) {
 | 
						|
		storage = FFmpeg::CreateFrameStorage(resize);
 | 
						|
	}
 | 
						|
	const auto format = AV_PIX_FMT_BGRA;
 | 
						|
	const auto hasDesiredFormat = (frame->format == format);
 | 
						|
	if (frameSize == storage.size() && hasDesiredFormat) {
 | 
						|
		static_assert(sizeof(uint32) == FFmpeg::kPixelBytesSize);
 | 
						|
		auto to = reinterpret_cast<uint32*>(storage.bits());
 | 
						|
		auto from = reinterpret_cast<const uint32*>(frame->data[0]);
 | 
						|
		const auto deltaTo = (storage.bytesPerLine() / sizeof(uint32))
 | 
						|
			- storage.width();
 | 
						|
		const auto deltaFrom = (frame->linesize[0] / sizeof(uint32))
 | 
						|
			- frame->width;
 | 
						|
		for (const auto y : ranges::view::ints(0, frame->height)) {
 | 
						|
			for (const auto x : ranges::view::ints(0, frame->width)) {
 | 
						|
				// Wipe out possible alpha values.
 | 
						|
				*to++ = 0xFF000000U | *from++;
 | 
						|
			}
 | 
						|
			to += deltaTo;
 | 
						|
			from += deltaFrom;
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		stream.swscale = MakeSwscalePointer(
 | 
						|
			frame,
 | 
						|
			resize,
 | 
						|
			&stream.swscale);
 | 
						|
		if (!stream.swscale) {
 | 
						|
			return QImage();
 | 
						|
		}
 | 
						|
 | 
						|
		// AV_NUM_DATA_POINTERS defined in AVFrame struct
 | 
						|
		uint8_t *data[AV_NUM_DATA_POINTERS] = { storage.bits(), nullptr };
 | 
						|
		int linesize[AV_NUM_DATA_POINTERS] = { storage.bytesPerLine(), 0 };
 | 
						|
 | 
						|
		const auto lines = sws_scale(
 | 
						|
			stream.swscale.get(),
 | 
						|
			frame->data,
 | 
						|
			frame->linesize,
 | 
						|
			0,
 | 
						|
			frame->height,
 | 
						|
			data,
 | 
						|
			linesize);
 | 
						|
		if (lines != resize.height()) {
 | 
						|
			LOG(("Streaming Error: "
 | 
						|
				"Unable to sws_scale to good size %1, got %2."
 | 
						|
				).arg(resize.height()
 | 
						|
				).arg(lines));
 | 
						|
			return QImage();
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	FFmpeg::ClearFrameMemory(frame);
 | 
						|
	return storage;
 | 
						|
}
 | 
						|
 | 
						|
QImage PrepareByRequest(
 | 
						|
		const QImage &original,
 | 
						|
		const FrameRequest &request,
 | 
						|
		QImage storage) {
 | 
						|
	Expects(!request.outer.isEmpty());
 | 
						|
 | 
						|
	if (!FFmpeg::GoodStorageForFrame(storage, request.outer)) {
 | 
						|
		storage = FFmpeg::CreateFrameStorage(request.outer);
 | 
						|
	}
 | 
						|
	{
 | 
						|
		Painter p(&storage);
 | 
						|
		PainterHighQualityEnabler hq(p);
 | 
						|
		p.drawImage(QRect(QPoint(), request.outer), original);
 | 
						|
	}
 | 
						|
	if ((request.corners & RectPart::AllCorners)
 | 
						|
		&& (request.radius != ImageRoundRadius::None)) {
 | 
						|
		Images::prepareRound(storage, request.radius, request.corners);
 | 
						|
	}
 | 
						|
	// #TODO streaming later full prepare support.
 | 
						|
	return storage;
 | 
						|
}
 | 
						|
 | 
						|
} // namespace Streaming
 | 
						|
} // namespace Media
 |