/* 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/audio/media_audio_ffmpeg_loader.h" #include "base/bytes.h" #include "core/file_location.h" #include "ffmpeg/ffmpeg_utility.h" #include "media/media_common.h" extern "C" { #include #include } // extern "C" namespace Media { namespace { using FFmpeg::AvErrorWrap; using FFmpeg::LogError; } // namespace #if !DA_FFMPEG_NEW_CHANNEL_LAYOUT uint64_t AbstractFFMpegLoader::ComputeChannelLayout( uint64_t channel_layout, int channels) { if (channel_layout) { if (av_get_channel_layout_nb_channels(channel_layout) == channels) { return channel_layout; } } return av_get_default_channel_layout(channels); } #endif // !DA_FFMPEG_NEW_CHANNEL_LAYOUT int64 AbstractFFMpegLoader::Mul(int64 value, AVRational rational) { return value * rational.num / rational.den; } bool AbstractFFMpegLoader::open(crl::time positionMs, float64 speed) { if (!AudioPlayerLoader::openFile()) { return false; } ioBuffer = (uchar *)av_malloc(FFmpeg::kAVBlockSize); if (!_data.isEmpty()) { ioContext = avio_alloc_context(ioBuffer, FFmpeg::kAVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::ReadData, 0, &AbstractFFMpegLoader::SeekData); } else if (!_bytes.empty()) { ioContext = avio_alloc_context(ioBuffer, FFmpeg::kAVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::ReadBytes, 0, &AbstractFFMpegLoader::SeekBytes); } else { ioContext = avio_alloc_context(ioBuffer, FFmpeg::kAVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::ReadFile, 0, &AbstractFFMpegLoader::SeekFile); } fmtContext = avformat_alloc_context(); if (!fmtContext) { LogError(u"avformat_alloc_context"_q); return false; } fmtContext->pb = ioContext; if (AvErrorWrap error = avformat_open_input(&fmtContext, 0, 0, 0)) { ioBuffer = nullptr; LogError(u"avformat_open_input"_q, error); return false; } _opened = true; if (AvErrorWrap error = avformat_find_stream_info(fmtContext, 0)) { LogError(u"avformat_find_stream_info"_q, error); return false; } streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); if (streamId < 0) { FFmpeg::LogError(u"av_find_best_stream"_q, AvErrorWrap(streamId)); return false; } const auto stream = fmtContext->streams[streamId]; const auto params = stream->codecpar; _samplesFrequency = params->sample_rate; if (stream->duration != AV_NOPTS_VALUE) { _duration = Mul(stream->duration * 1000, stream->time_base); } else { _duration = Mul(fmtContext->duration * 1000, { 1, AV_TIME_BASE }); } _startedAtSample = (positionMs * _samplesFrequency) / 1000LL; return true; } AbstractFFMpegLoader::~AbstractFFMpegLoader() { if (_opened) { avformat_close_input(&fmtContext); } if (ioContext) { av_freep(&ioContext->buffer); av_freep(&ioContext); } else if (ioBuffer) { av_freep(&ioBuffer); } if (fmtContext) avformat_free_context(fmtContext); } int AbstractFFMpegLoader::ReadData(void *opaque, uint8_t *buf, int buf_size) { auto l = reinterpret_cast(opaque); auto nbytes = qMin(l->_data.size() - l->_dataPos, int32(buf_size)); if (nbytes <= 0) { return AVERROR_EOF; } memcpy(buf, l->_data.constData() + l->_dataPos, nbytes); l->_dataPos += nbytes; return nbytes; } int64_t AbstractFFMpegLoader::SeekData(void *opaque, int64_t offset, int whence) { auto l = reinterpret_cast(opaque); int32 newPos = -1; switch (whence) { case SEEK_SET: newPos = offset; break; case SEEK_CUR: newPos = l->_dataPos + offset; break; case SEEK_END: newPos = l->_data.size() + offset; break; case AVSEEK_SIZE: { // Special whence for determining filesize without any seek. return l->_data.size(); } break; } if (newPos < 0 || newPos > l->_data.size()) { return -1; } l->_dataPos = newPos; return l->_dataPos; } int AbstractFFMpegLoader::ReadBytes(void *opaque, uint8_t *buf, int buf_size) { auto l = reinterpret_cast(opaque); auto nbytes = qMin(static_cast(l->_bytes.size()) - l->_dataPos, buf_size); if (nbytes <= 0) { return AVERROR_EOF; } memcpy(buf, l->_bytes.data() + l->_dataPos, nbytes); l->_dataPos += nbytes; return nbytes; } int64_t AbstractFFMpegLoader::SeekBytes(void *opaque, int64_t offset, int whence) { auto l = reinterpret_cast(opaque); int32 newPos = -1; switch (whence) { case SEEK_SET: newPos = offset; break; case SEEK_CUR: newPos = l->_dataPos + offset; break; case SEEK_END: newPos = static_cast(l->_bytes.size()) + offset; break; case AVSEEK_SIZE: { // Special whence for determining filesize without any seek. return l->_bytes.size(); } break; } if (newPos < 0 || newPos > l->_bytes.size()) { return -1; } l->_dataPos = newPos; return l->_dataPos; } int AbstractFFMpegLoader::ReadFile(void *opaque, uint8_t *buf, int buf_size) { auto l = reinterpret_cast(opaque); int ret = l->_f.read((char *)(buf), buf_size); switch (ret) { case -1: return AVERROR_EXTERNAL; case 0: return AVERROR_EOF; default: return ret; } } int64_t AbstractFFMpegLoader::SeekFile(void *opaque, int64_t offset, int whence) { auto l = reinterpret_cast(opaque); switch (whence) { case SEEK_SET: return l->_f.seek(offset) ? l->_f.pos() : -1; case SEEK_CUR: return l->_f.seek(l->_f.pos() + offset) ? l->_f.pos() : -1; case SEEK_END: return l->_f.seek(l->_f.size() + offset) ? l->_f.pos() : -1; case AVSEEK_SIZE: { // Special whence for determining filesize without any seek. return l->_f.size(); } break; } return -1; } AbstractAudioFFMpegLoader::AbstractAudioFFMpegLoader( const Core::FileLocation &file, const QByteArray &data, bytes::vector &&buffer) : AbstractFFMpegLoader(file, data, std::move(buffer)) , _frame(FFmpeg::MakeFramePointer()) { } void AbstractAudioFFMpegLoader::dropFramesTill(int64 samples) { const auto isAfter = [&](const EnqueuedFrame &frame) { return frame.position > samples; }; const auto from = begin(_framesQueued); const auto after = ranges::find_if(_framesQueued, isAfter); if (from == after) { return; } const auto till = after - 1; const auto erasing = till - from; if (erasing > 0) { if (_framesQueuedIndex >= 0) { Assert(_framesQueuedIndex >= erasing); _framesQueuedIndex -= erasing; } _framesQueued.erase(from, till); if (_framesQueued.empty()) { _framesQueuedIndex = -1; } } } int64 AbstractAudioFFMpegLoader::startReadingQueuedFrames(float64 newSpeed) { changeSpeedFilter(newSpeed); if (_framesQueued.empty()) { _framesQueuedIndex = -1; return -1; } _framesQueuedIndex = 0; return _framesQueued.front().position; } bool AbstractAudioFFMpegLoader::initUsingContext( not_null context, float64 speed) { _swrSrcSampleFormat = context->sample_fmt; #if DA_FFMPEG_NEW_CHANNEL_LAYOUT const AVChannelLayout mono = AV_CHANNEL_LAYOUT_MONO; const AVChannelLayout stereo = AV_CHANNEL_LAYOUT_STEREO; const auto useMono = !av_channel_layout_compare( &context->ch_layout, &mono); const auto useStereo = !av_channel_layout_compare( &context->ch_layout, &stereo); const auto copyDstChannelLayout = [&] { av_channel_layout_copy(&_swrDstChannelLayout, &context->ch_layout); }; #else // DA_FFMPEG_NEW_CHANNEL_LAYOUT const auto layout = ComputeChannelLayout( context->channel_layout, context->channels); if (!layout) { LOG(("Audio Error: Unknown channel layout %1 for %2 channels." ).arg(context->channel_layout ).arg(context->channels )); return false; } const auto useMono = (layout == AV_CH_LAYOUT_MONO); const auto useStereo = (layout == AV_CH_LAYOUT_STEREO); const auto copyDstChannelLayout = [&] { _swrDstChannelLayout = layout; }; #endif // DA_FFMPEG_NEW_CHANNEL_LAYOUT if (useMono) { switch (_swrSrcSampleFormat) { case AV_SAMPLE_FMT_U8: case AV_SAMPLE_FMT_U8P: _swrDstSampleFormat = _swrSrcSampleFormat; copyDstChannelLayout(); _outputChannels = 1; _outputSampleSize = 1; _outputFormat = AL_FORMAT_MONO8; break; case AV_SAMPLE_FMT_S16: case AV_SAMPLE_FMT_S16P: _swrDstSampleFormat = _swrSrcSampleFormat; copyDstChannelLayout(); _outputChannels = 1; _outputSampleSize = sizeof(uint16); _outputFormat = AL_FORMAT_MONO16; break; } } else if (useStereo) { switch (_swrSrcSampleFormat) { case AV_SAMPLE_FMT_U8: _swrDstSampleFormat = _swrSrcSampleFormat; copyDstChannelLayout(); _outputChannels = 2; _outputSampleSize = 2; _outputFormat = AL_FORMAT_STEREO8; break; case AV_SAMPLE_FMT_S16: _swrDstSampleFormat = _swrSrcSampleFormat; copyDstChannelLayout(); _outputChannels = 2; _outputSampleSize = 2 * sizeof(uint16); _outputFormat = AL_FORMAT_STEREO16; break; } } createSpeedFilter(speed); return true; } auto AbstractAudioFFMpegLoader::replaceFrameAndRead( FFmpeg::FramePointer frame) -> ReadResult { _frame = std::move(frame); return readFromReadyFrame(); } auto AbstractAudioFFMpegLoader::readFromReadyContext( not_null context) -> ReadResult { if (_filterGraph) { AvErrorWrap error = av_buffersink_get_frame( _filterSink, _filteredFrame.get()); if (!error) { if (!_filteredFrame->nb_samples) { return ReadError::Retry; } return bytes::const_span( reinterpret_cast( _filteredFrame->extended_data[0]), _filteredFrame->nb_samples * _outputSampleSize); } else if (error.code() == AVERROR_EOF) { return ReadError::EndOfFile; } else if (error.code() != AVERROR(EAGAIN)) { LogError(u"av_buffersink_get_frame"_q, error); return ReadError::Other; } } using Enqueued = not_null; const auto queueResult = fillFrameFromQueued(); if (queueResult == ReadError::RetryNotQueued) { return ReadError::RetryNotQueued; } else if (const auto enqueued = std::get_if(&queueResult)) { const auto raw = (*enqueued)->frame.get(); Assert(frameHasDesiredFormat(raw)); return readOrBufferForFilter(raw, (*enqueued)->samples); } const auto queueError = v::get(queueResult); AvErrorWrap error = (queueError == ReadError::EndOfFile) ? AVERROR_EOF : avcodec_receive_frame(context, _frame.get()); if (!error) { return readFromReadyFrame(); } if (error.code() == AVERROR_EOF) { enqueueFramesFinished(); if (!_filterGraph) { return ReadError::EndOfFile; } AvErrorWrap error = av_buffersrc_add_frame(_filterSrc, nullptr); if (!error) { return ReadError::Retry; } LogError(u"av_buffersrc_add_frame"_q, error); return ReadError::Other; } else if (error.code() != AVERROR(EAGAIN)) { LogError(u"avcodec_receive_frame"_q, error); return ReadError::Other; } return ReadError::Wait; } auto AbstractAudioFFMpegLoader::fillFrameFromQueued() -> std::variant, ReadError> { if (_framesQueuedIndex == _framesQueued.size()) { _framesQueuedIndex = -1; return ReadError::RetryNotQueued; } else if (_framesQueuedIndex < 0) { return ReadError::Wait; } const auto &queued = _framesQueued[_framesQueuedIndex]; ++_framesQueuedIndex; if (!queued.frame) { return ReadError::EndOfFile; } return &queued; } bool AbstractAudioFFMpegLoader::frameHasDesiredFormat( not_null frame) const { const auto sameChannelLayout = [&] { #if DA_FFMPEG_NEW_CHANNEL_LAYOUT return !av_channel_layout_compare( &frame->ch_layout, &_swrDstChannelLayout); #else // DA_FFMPEG_NEW_CHANNEL_LAYOUT const auto frameChannelLayout = ComputeChannelLayout( frame->channel_layout, frame->channels); return (frameChannelLayout == _swrDstChannelLayout); #endif // DA_FFMPEG_NEW_CHANNEL_LAYOUT }; return true && (frame->format == _swrDstSampleFormat) && (frame->sample_rate == _swrDstRate) && sameChannelLayout(); } bool AbstractAudioFFMpegLoader::initResampleForFrame() { #if DA_FFMPEG_NEW_CHANNEL_LAYOUT const auto bad = !_frame->ch_layout.nb_channels; #else // DA_FFMPEG_NEW_CHANNEL_LAYOUT const auto frameChannelLayout = ComputeChannelLayout( _frame->channel_layout, _frame->channels); const auto bad = !frameChannelLayout; #endif // DA_FFMPEG_NEW_CHANNEL_LAYOUT if (bad) { LOG(("Audio Error: " "Unknown channel layout for frame in file '%1', " "data size '%2'" ).arg(_file.name() ).arg(_data.size() )); return false; } else if (_frame->format == -1) { LOG(("Audio Error: " "Unknown frame format in file '%1', data size '%2'" ).arg(_file.name() ).arg(_data.size() )); return false; } else if (_swrContext) { const auto sameChannelLayout = [&] { #if DA_FFMPEG_NEW_CHANNEL_LAYOUT return !av_channel_layout_compare( &_frame->ch_layout, &_swrSrcChannelLayout); #else // DA_FFMPEG_NEW_CHANNEL_LAYOUT return (frameChannelLayout == _swrSrcChannelLayout); #endif // DA_FFMPEG_NEW_CHANNEL_LAYOUT }; if (true && (_frame->format == _swrSrcSampleFormat) && (_frame->sample_rate == _swrSrcRate) && sameChannelLayout()) { return true; } swr_close(_swrContext); } _swrSrcSampleFormat = static_cast(_frame->format); #if DA_FFMPEG_NEW_CHANNEL_LAYOUT av_channel_layout_copy(&_swrSrcChannelLayout, &_frame->ch_layout); #else // DA_FFMPEG_NEW_CHANNEL_LAYOUT _swrSrcChannelLayout = frameChannelLayout; #endif // DA_FFMPEG_NEW_CHANNEL_LAYOUT _swrSrcRate = _frame->sample_rate; return initResampleUsingFormat(); } bool AbstractAudioFFMpegLoader::initResampleUsingFormat() { AvErrorWrap error = 0; #if DA_FFMPEG_NEW_CHANNEL_LAYOUT error = swr_alloc_set_opts2( &_swrContext, &_swrDstChannelLayout, _swrDstSampleFormat, _swrDstRate, &_swrSrcChannelLayout, _swrSrcSampleFormat, _swrSrcRate, 0, nullptr); #else // DA_FFMPEG_NEW_CHANNEL_LAYOUT _swrContext = swr_alloc_set_opts( _swrContext, _swrDstChannelLayout, _swrDstSampleFormat, _swrDstRate, _swrSrcChannelLayout, _swrSrcSampleFormat, _swrSrcRate, 0, nullptr); #endif // DA_FFMPEG_NEW_CHANNEL_LAYOUT if (error || !_swrContext) { LogError(u"swr_alloc_set_opts2"_q, error); return false; } else if (AvErrorWrap error = swr_init(_swrContext)) { LogError(u"swr_init"_q, error); return false; } _resampledFrame = nullptr; _resampledFrameCapacity = 0; return true; } bool AbstractAudioFFMpegLoader::ensureResampleSpaceAvailable(int samples) { const auto enlarge = (_resampledFrameCapacity < samples); if (!_resampledFrame) { _resampledFrame = FFmpeg::MakeFramePointer(); } else if (enlarge || !av_frame_is_writable(_resampledFrame.get())) { av_frame_unref(_resampledFrame.get()); } else { return true; } const auto allocate = std::max(samples, int(av_rescale_rnd( FFmpeg::kAVBlockSize / _outputSampleSize, _swrDstRate, _swrSrcRate, AV_ROUND_UP))); _resampledFrame->sample_rate = _swrDstRate; _resampledFrame->format = _swrDstSampleFormat; #if DA_FFMPEG_NEW_CHANNEL_LAYOUT av_channel_layout_copy( &_resampledFrame->ch_layout, &_swrDstChannelLayout); #else // DA_FFMPEG_NEW_CHANNEL_LAYOUT _resampledFrame->channel_layout = _swrDstChannelLayout; #endif // DA_FFMPEG_NEW_CHANNEL_LAYOUT _resampledFrame->nb_samples = allocate; if (AvErrorWrap error = av_frame_get_buffer(_resampledFrame.get(), 0)) { LogError(u"av_frame_get_buffer"_q, error); return false; } _resampledFrameCapacity = allocate; return true; } bool AbstractAudioFFMpegLoader::changeSpeedFilter(float64 speed) { speed = std::clamp(speed, kSpeedMin, kSpeedMax); if (EqualSpeeds(_filterSpeed, speed)) { return false; } avfilter_graph_free(&_filterGraph); const auto guard = gsl::finally([&] { if (!_filterGraph) { _filteredFrame = nullptr; _filterSpeed = 1.; } }); createSpeedFilter(speed); return true; } void AbstractAudioFFMpegLoader::createSpeedFilter(float64 speed) { Expects(!_filterGraph); if (EqualSpeeds(speed, 1.)) { return; } const auto abuffer = avfilter_get_by_name("abuffer"); const auto abuffersink = avfilter_get_by_name("abuffersink"); const auto atempo = avfilter_get_by_name("atempo"); if (!abuffer || !abuffersink || !atempo) { LOG(("FFmpeg Error: Could not find abuffer / abuffersink /atempo.")); return; } auto graph = avfilter_graph_alloc(); if (!graph) { LOG(("FFmpeg Error: Unable to create filter graph.")); return; } const auto guard = gsl::finally([&] { avfilter_graph_free(&graph); }); _filterSrc = avfilter_graph_alloc_filter(graph, abuffer, "src"); _atempo = avfilter_graph_alloc_filter(graph, atempo, "atempo"); _filterSink = avfilter_graph_alloc_filter(graph, abuffersink, "sink"); if (!_filterSrc || !atempo || !_filterSink) { LOG(("FFmpeg Error: " "Could not allocate abuffer / abuffersink /atempo.")); return; } char layout[64] = { 0 }; #if DA_FFMPEG_NEW_CHANNEL_LAYOUT av_channel_layout_describe( &_swrDstChannelLayout, layout, sizeof(layout)); #else // DA_FFMPEG_NEW_CHANNEL_LAYOUT av_get_channel_layout_string( layout, sizeof(layout), 0, _swrDstChannelLayout); #endif // DA_FFMPEG_NEW_CHANNEL_LAYOUT av_opt_set( _filterSrc, "channel_layout", layout, AV_OPT_SEARCH_CHILDREN); av_opt_set_sample_fmt( _filterSrc, "sample_fmt", _swrDstSampleFormat, AV_OPT_SEARCH_CHILDREN); av_opt_set_q( _filterSrc, "time_base", AVRational{ 1, _swrDstRate }, AV_OPT_SEARCH_CHILDREN); av_opt_set_int( _filterSrc, "sample_rate", _swrDstRate, AV_OPT_SEARCH_CHILDREN); av_opt_set_double( _atempo, "tempo", speed, AV_OPT_SEARCH_CHILDREN); AvErrorWrap error = 0; if ((error = avfilter_init_str(_filterSrc, nullptr))) { LogError(u"avfilter_init_str(src)"_q, error); return; } else if ((error = avfilter_init_str(_atempo, nullptr))) { LogError(u"avfilter_init_str(atempo)"_q, error); avfilter_graph_free(&graph); return; } else if ((error = avfilter_init_str(_filterSink, nullptr))) { LogError(u"avfilter_init_str(sink)"_q, error); avfilter_graph_free(&graph); return; } else if ((error = avfilter_link(_filterSrc, 0, _atempo, 0))) { LogError(u"avfilter_link(src->atempo)"_q, error); avfilter_graph_free(&graph); return; } else if ((error = avfilter_link(_atempo, 0, _filterSink, 0))) { LogError(u"avfilter_link(atempo->sink)"_q, error); avfilter_graph_free(&graph); return; } else if ((error = avfilter_graph_config(graph, nullptr))) { LogError("avfilter_link(atempo->sink)"_q, error); avfilter_graph_free(&graph); return; } _filterGraph = base::take(graph); _filteredFrame = FFmpeg::MakeFramePointer(); _filterSpeed = speed; } void AbstractAudioFFMpegLoader::enqueueNormalFrame( not_null frame, int64 samples) { if (_framesQueuedIndex >= 0) { return; } if (!samples) { samples = frame->nb_samples; } _framesQueued.push_back({ .position = startedAtSample() + _framesQueuedSamples, .samples = samples, .frame = FFmpeg::DuplicateFramePointer(frame), }); _framesQueuedSamples += samples; } void AbstractAudioFFMpegLoader::enqueueFramesFinished() { if (_framesQueuedIndex >= 0) { return; } _framesQueued.push_back({ .position = startedAtSample() + _framesQueuedSamples, }); } auto AbstractAudioFFMpegLoader::readFromReadyFrame() -> ReadResult { const auto raw = _frame.get(); if (frameHasDesiredFormat(raw)) { if (!raw->nb_samples) { return ReadError::Retry; } return readOrBufferForFilter(raw, raw->nb_samples); } else if (!initResampleForFrame()) { return ReadError::Other; } const auto maxSamples = av_rescale_rnd( swr_get_delay(_swrContext, _swrSrcRate) + _frame->nb_samples, _swrDstRate, _swrSrcRate, AV_ROUND_UP); if (!ensureResampleSpaceAvailable(maxSamples)) { return ReadError::Other; } const auto samples = swr_convert( _swrContext, (uint8_t**)_resampledFrame->extended_data, maxSamples, (const uint8_t **)_frame->extended_data, _frame->nb_samples); if (AvErrorWrap error = samples) { LogError(u"swr_convert"_q, error); return ReadError::Other; } else if (!samples) { return ReadError::Retry; } return readOrBufferForFilter(_resampledFrame.get(), samples); } auto AbstractAudioFFMpegLoader::readOrBufferForFilter( not_null frame, int64 samplesOverride) -> ReadResult { enqueueNormalFrame(frame, samplesOverride); const auto was = frame->nb_samples; frame->nb_samples = samplesOverride; const auto guard = gsl::finally([&] { frame->nb_samples = was; }); if (!_filterGraph) { return bytes::const_span( reinterpret_cast(frame->extended_data[0]), frame->nb_samples * _outputSampleSize); } AvErrorWrap error = av_buffersrc_add_frame_flags( _filterSrc, frame, AV_BUFFERSRC_FLAG_KEEP_REF); if (error) { LogError(u"av_buffersrc_add_frame_flags"_q, error); return ReadError::Other; } return ReadError::Retry; } AbstractAudioFFMpegLoader::~AbstractAudioFFMpegLoader() { if (_filterGraph) { avfilter_graph_free(&_filterGraph); } if (_swrContext) { swr_free(&_swrContext); } } FFMpegLoader::FFMpegLoader( const Core::FileLocation &file, const QByteArray &data, bytes::vector &&buffer) : AbstractAudioFFMpegLoader(file, data, std::move(buffer)) { } bool FFMpegLoader::open(crl::time positionMs, float64 speed) { return AbstractFFMpegLoader::open(positionMs) && openCodecContext() && initUsingContext(_codecContext, speed) && seekTo(positionMs); } bool FFMpegLoader::openCodecContext() { _codecContext = avcodec_alloc_context3(nullptr); if (!_codecContext) { LOG(("Audio Error: " "Unable to avcodec_alloc_context3 for file '%1', data size '%2'" ).arg(_file.name() ).arg(_data.size() )); return false; } const auto stream = fmtContext->streams[streamId]; AvErrorWrap error = avcodec_parameters_to_context( _codecContext, stream->codecpar); if (error) { LogError(u"avcodec_parameters_to_context"_q, error); return false; } _codecContext->pkt_timebase = stream->time_base; av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); if (AvErrorWrap error = avcodec_open2(_codecContext, codec, 0)) { LogError(u"avcodec_open2"_q, error); return false; } return true; } bool FFMpegLoader::seekTo(crl::time positionMs) { if (positionMs) { const auto stream = fmtContext->streams[streamId]; const auto timeBase = stream->time_base; const auto timeStamp = (positionMs * timeBase.den) / (1000LL * timeBase.num); const auto flags1 = AVSEEK_FLAG_ANY; if (av_seek_frame(fmtContext, streamId, timeStamp, flags1) < 0) { const auto flags2 = 0; if (av_seek_frame(fmtContext, streamId, timeStamp, flags2) < 0) { } } } return true; } FFMpegLoader::ReadResult FFMpegLoader::readMore() { if (_readTillEnd) { return ReadError::EndOfFile; } const auto readResult = readFromReadyContext(_codecContext); if (readResult != ReadError::Wait) { if (readResult == ReadError::EndOfFile) { _readTillEnd = true; } return readResult; } if (AvErrorWrap error = av_read_frame(fmtContext, &_packet)) { if (error.code() != AVERROR_EOF) { LogError(u"av_read_frame"_q, error); return ReadError::Other; } error = avcodec_send_packet(_codecContext, nullptr); // drain if (!error) { return ReadError::Retry; } LogError(u"avcodec_send_packet"_q, error); return ReadError::Other; } if (_packet.stream_index == streamId) { AvErrorWrap error = avcodec_send_packet(_codecContext, &_packet); if (error) { av_packet_unref(&_packet); LogError(u"avcodec_send_packet"_q, error); // There is a sample voice message where skipping such packet // results in a crash (read_access to nullptr) in swr_convert(). //if (error.code() == AVERROR_INVALIDDATA) { // return ReadResult::Retry; // try to skip bad packet //} return ReadError::Other; } } av_packet_unref(&_packet); return ReadError::Retry; } FFMpegLoader::~FFMpegLoader() { if (_codecContext) { avcodec_free_context(&_codecContext); } } } // namespace Media