tdesktop/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp
John Preston 08167a6a91 Removed #include "stdafx.h" from all files.
Currently the build without implicitly included precompiled header
is not supported anyway (because Qt MOC source files do not include
stdafx.h, they include plain headers).

So when we decide to support building without implicitly included
precompiled headers we'll have to fix all the headers anyway.
2017-03-04 12:27:52 +03:00

211 lines
8 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(uint64 videoPlayId, std::unique_ptr<VideoSoundData> &&data) : AudioPlayerLoader(FileLocation(), QByteArray())
, _videoPlayId(videoPlayId)
, _parentData(std::move(data)) {
_frame = av_frame_alloc();
}
bool ChildFFMpegLoader::open(qint64 &position) {
int res = 0;
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
uint64_t layout = _parentData->context->channel_layout;
_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);
}