Use AudioMsgId instead of videoPlayId. Any audio track now can be a child loader track of some video clip. Use Type::Voice instead of Type::Video for round video messages. Video messages play / pause / resume the same way as voice messages.
		
			
				
	
	
		
			218 lines
		
	
	
	
		
			8.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
	
		
			8.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
This file is part of Telegram Desktop,
 | 
						|
the official desktop version of Telegram messaging app, see https://telegram.org
 | 
						|
 | 
						|
Telegram Desktop is free software: you can redistribute it and/or modify
 | 
						|
it under the terms of the GNU General Public License as published by
 | 
						|
the Free Software Foundation, either version 3 of the License, or
 | 
						|
(at your option) any later version.
 | 
						|
 | 
						|
It is distributed in the hope that it will be useful,
 | 
						|
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
						|
GNU General Public License for more details.
 | 
						|
 | 
						|
In addition, as a special exception, the copyright holders give permission
 | 
						|
to link the code of portions of this program with the OpenSSL library.
 | 
						|
 | 
						|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
 | 
						|
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 | 
						|
*/
 | 
						|
#include "media/media_child_ffmpeg_loader.h"
 | 
						|
 | 
						|
constexpr AVSampleFormat AudioToFormat = AV_SAMPLE_FMT_S16;
 | 
						|
constexpr int64_t AudioToChannelLayout = AV_CH_LAYOUT_STEREO;
 | 
						|
constexpr int32 AudioToChannels = 2;
 | 
						|
 | 
						|
VideoSoundData::~VideoSoundData() {
 | 
						|
	if (context) {
 | 
						|
		avcodec_close(context);
 | 
						|
		avcodec_free_context(&context);
 | 
						|
		context = nullptr;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
ChildFFMpegLoader::ChildFFMpegLoader(std::unique_ptr<VideoSoundData> &&data) : AudioPlayerLoader(FileLocation(), QByteArray(), base::byte_vector())
 | 
						|
, _parentData(std::move(data)) {
 | 
						|
	_frame = av_frame_alloc();
 | 
						|
}
 | 
						|
 | 
						|
bool ChildFFMpegLoader::open(qint64 &position) {
 | 
						|
	int res = 0;
 | 
						|
	char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
 | 
						|
 | 
						|
	auto layout = _parentData->context->channel_layout;
 | 
						|
	if (!layout) {
 | 
						|
		auto channelsCount = _parentData->context->channels;
 | 
						|
		switch (channelsCount) {
 | 
						|
		case 1: layout = AV_CH_LAYOUT_MONO; break;
 | 
						|
		case 2: layout = AV_CH_LAYOUT_STEREO; break;
 | 
						|
		default: LOG(("Audio Error: Unknown channel layout for %1 channels.").arg(channelsCount)); break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	_inputFormat = _parentData->context->sample_fmt;
 | 
						|
	switch (layout) {
 | 
						|
	case AV_CH_LAYOUT_MONO:
 | 
						|
		switch (_inputFormat) {
 | 
						|
		case AV_SAMPLE_FMT_U8:
 | 
						|
		case AV_SAMPLE_FMT_U8P: _format = AL_FORMAT_MONO8; _sampleSize = 1; break;
 | 
						|
		case AV_SAMPLE_FMT_S16:
 | 
						|
		case AV_SAMPLE_FMT_S16P: _format = AL_FORMAT_MONO16; _sampleSize = sizeof(uint16); break;
 | 
						|
		default:
 | 
						|
			_sampleSize = -1; // convert needed
 | 
						|
		break;
 | 
						|
		}
 | 
						|
	break;
 | 
						|
	case AV_CH_LAYOUT_STEREO:
 | 
						|
		switch (_inputFormat) {
 | 
						|
		case AV_SAMPLE_FMT_U8: _format = AL_FORMAT_STEREO8; _sampleSize = 2; break;
 | 
						|
		case AV_SAMPLE_FMT_S16: _format = AL_FORMAT_STEREO16; _sampleSize = 2 * sizeof(uint16); break;
 | 
						|
		default:
 | 
						|
			_sampleSize = -1; // convert needed
 | 
						|
		break;
 | 
						|
		}
 | 
						|
	break;
 | 
						|
	default:
 | 
						|
		_sampleSize = -1; // convert needed
 | 
						|
	break;
 | 
						|
	}
 | 
						|
	if (_parentData->frequency != 44100 && _parentData->frequency != 48000) {
 | 
						|
		_sampleSize = -1; // convert needed
 | 
						|
	}
 | 
						|
 | 
						|
	if (_sampleSize < 0) {
 | 
						|
		_swrContext = swr_alloc();
 | 
						|
		if (!_swrContext) {
 | 
						|
			LOG(("Audio Error: Unable to swr_alloc for file '%1', data size '%2'").arg(_file.name()).arg(_data.size()));
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		int64_t src_ch_layout = layout, dst_ch_layout = AudioToChannelLayout;
 | 
						|
		_srcRate = _parentData->frequency;
 | 
						|
		AVSampleFormat src_sample_fmt = _inputFormat, dst_sample_fmt = AudioToFormat;
 | 
						|
		_dstRate = (_parentData->frequency != 44100 && _parentData->frequency != 48000) ? Media::Player::kDefaultFrequency : _parentData->frequency;
 | 
						|
 | 
						|
		av_opt_set_int(_swrContext, "in_channel_layout", src_ch_layout, 0);
 | 
						|
		av_opt_set_int(_swrContext, "in_sample_rate", _srcRate, 0);
 | 
						|
		av_opt_set_sample_fmt(_swrContext, "in_sample_fmt", src_sample_fmt, 0);
 | 
						|
		av_opt_set_int(_swrContext, "out_channel_layout", dst_ch_layout, 0);
 | 
						|
		av_opt_set_int(_swrContext, "out_sample_rate", _dstRate, 0);
 | 
						|
		av_opt_set_sample_fmt(_swrContext, "out_sample_fmt", dst_sample_fmt, 0);
 | 
						|
 | 
						|
		if ((res = swr_init(_swrContext)) < 0) {
 | 
						|
			LOG(("Audio Error: Unable to swr_init for file '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		_sampleSize = AudioToChannels * sizeof(short);
 | 
						|
		_parentData->frequency = _dstRate;
 | 
						|
		_parentData->length = av_rescale_rnd(_parentData->length, _dstRate, _srcRate, AV_ROUND_UP);
 | 
						|
		position = av_rescale_rnd(position, _dstRate, _srcRate, AV_ROUND_DOWN);
 | 
						|
		_format = AL_FORMAT_STEREO16;
 | 
						|
 | 
						|
		_maxResampleSamples = av_rescale_rnd(AVBlockSize / _sampleSize, _dstRate, _srcRate, AV_ROUND_UP);
 | 
						|
		if ((res = av_samples_alloc_array_and_samples(&_dstSamplesData, 0, AudioToChannels, _maxResampleSamples, AudioToFormat, 0)) < 0) {
 | 
						|
			LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
AudioPlayerLoader::ReadResult ChildFFMpegLoader::readMore(QByteArray &result, int64 &samplesAdded) {
 | 
						|
	int res;
 | 
						|
 | 
						|
	av_frame_unref(_frame);
 | 
						|
	res = avcodec_receive_frame(_parentData->context, _frame);
 | 
						|
	if (res >= 0) {
 | 
						|
		return readFromReadyFrame(result, samplesAdded);
 | 
						|
	}
 | 
						|
 | 
						|
	if (res == AVERROR_EOF) {
 | 
						|
		return ReadResult::EndOfFile;
 | 
						|
	} else if (res != AVERROR(EAGAIN)) {
 | 
						|
		char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
 | 
						|
		LOG(("Audio Error: Unable to avcodec_receive_frame() file '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
 | 
						|
		return ReadResult::Error;
 | 
						|
	}
 | 
						|
 | 
						|
	if (_queue.isEmpty()) {
 | 
						|
		return _eofReached ? ReadResult::EndOfFile : ReadResult::Wait;
 | 
						|
	}
 | 
						|
 | 
						|
	AVPacket packet;
 | 
						|
	FFMpeg::packetFromDataWrap(packet, _queue.dequeue());
 | 
						|
 | 
						|
	_eofReached = FFMpeg::isNullPacket(packet);
 | 
						|
	if (_eofReached) {
 | 
						|
		avcodec_send_packet(_parentData->context, nullptr); // drain
 | 
						|
		return ReadResult::Ok;
 | 
						|
	}
 | 
						|
 | 
						|
	res = avcodec_send_packet(_parentData->context, &packet);
 | 
						|
	if (res < 0) {
 | 
						|
		FFMpeg::freePacket(&packet);
 | 
						|
 | 
						|
		char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
 | 
						|
		LOG(("Audio Error: Unable to avcodec_send_packet() file '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
 | 
						|
		// There is a sample voice message where skipping such packet
 | 
						|
		// results in a crash (read_access to nullptr) in swr_convert().
 | 
						|
		//if (res == AVERROR_INVALIDDATA) {
 | 
						|
		//	return ReadResult::NotYet; // try to skip bad packet
 | 
						|
		//}
 | 
						|
		return ReadResult::Error;
 | 
						|
	}
 | 
						|
	FFMpeg::freePacket(&packet);
 | 
						|
	return ReadResult::Ok;
 | 
						|
}
 | 
						|
 | 
						|
AudioPlayerLoader::ReadResult ChildFFMpegLoader::readFromReadyFrame(QByteArray &result, int64 &samplesAdded) {
 | 
						|
	int res = 0;
 | 
						|
 | 
						|
	if (_dstSamplesData) { // convert needed
 | 
						|
		int64_t dstSamples = av_rescale_rnd(swr_get_delay(_swrContext, _srcRate) + _frame->nb_samples, _dstRate, _srcRate, AV_ROUND_UP);
 | 
						|
		if (dstSamples > _maxResampleSamples) {
 | 
						|
			_maxResampleSamples = dstSamples;
 | 
						|
			av_freep(&_dstSamplesData[0]);
 | 
						|
			if ((res = av_samples_alloc(_dstSamplesData, 0, AudioToChannels, _maxResampleSamples, AudioToFormat, 1)) < 0) {
 | 
						|
				char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
 | 
						|
				LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
 | 
						|
				return ReadResult::Error;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if ((res = swr_convert(_swrContext, _dstSamplesData, dstSamples, (const uint8_t**)_frame->extended_data, _frame->nb_samples)) < 0) {
 | 
						|
			char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
 | 
						|
			LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
 | 
						|
			return ReadResult::Error;
 | 
						|
		}
 | 
						|
		int32 resultLen = av_samples_get_buffer_size(0, AudioToChannels, res, AudioToFormat, 1);
 | 
						|
		result.append((const char*)_dstSamplesData[0], resultLen);
 | 
						|
		samplesAdded += resultLen / _sampleSize;
 | 
						|
	} else {
 | 
						|
		result.append((const char*)_frame->extended_data[0], _frame->nb_samples * _sampleSize);
 | 
						|
		samplesAdded += _frame->nb_samples;
 | 
						|
	}
 | 
						|
	return ReadResult::Ok;
 | 
						|
}
 | 
						|
 | 
						|
void ChildFFMpegLoader::enqueuePackets(QQueue<FFMpeg::AVPacketDataWrap> &packets) {
 | 
						|
	_queue += std::move(packets);
 | 
						|
	packets.clear();
 | 
						|
}
 | 
						|
 | 
						|
ChildFFMpegLoader::~ChildFFMpegLoader() {
 | 
						|
	auto queue = base::take(_queue);
 | 
						|
	for (auto &packetData : queue) {
 | 
						|
		AVPacket packet;
 | 
						|
		FFMpeg::packetFromDataWrap(packet, packetData);
 | 
						|
		FFMpeg::freePacket(&packet);
 | 
						|
	}
 | 
						|
	if (_swrContext) swr_free(&_swrContext);
 | 
						|
	if (_dstSamplesData) {
 | 
						|
		if (_dstSamplesData[0]) {
 | 
						|
			av_freep(&_dstSamplesData[0]);
 | 
						|
		}
 | 
						|
		av_freep(&_dstSamplesData);
 | 
						|
	}
 | 
						|
	av_frame_free(&_frame);
 | 
						|
}
 |