|
| 1 | +/* |
| 2 | + * Copyright (c) 2025, Gregory Bertilson <gregory@ladybird.org> |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: BSD-2-Clause |
| 5 | + */ |
| 6 | + |
| 7 | +#include <LibCore/System.h> |
| 8 | +#include <LibMedia/AudioBlock.h> |
| 9 | +#include <LibMedia/FFmpeg/FFmpegHelpers.h> |
| 10 | + |
| 11 | +#include "FFmpegAudioDecoder.h" |
| 12 | + |
| 13 | +namespace Media::FFmpeg { |
| 14 | + |
| 15 | +DecoderErrorOr<NonnullOwnPtr<FFmpegAudioDecoder>> FFmpegAudioDecoder::try_create(CodecID codec_id, ReadonlyBytes codec_initialization_data) |
| 16 | +{ |
| 17 | + AVCodecContext* codec_context = nullptr; |
| 18 | + AVPacket* packet = nullptr; |
| 19 | + AVFrame* frame = nullptr; |
| 20 | + ArmedScopeGuard memory_guard { |
| 21 | + [&] { |
| 22 | + avcodec_free_context(&codec_context); |
| 23 | + av_packet_free(&packet); |
| 24 | + av_frame_free(&frame); |
| 25 | + } |
| 26 | + }; |
| 27 | + |
| 28 | + auto ff_codec_id = ffmpeg_codec_id_from_media_codec_id(codec_id); |
| 29 | + auto const* codec = avcodec_find_decoder(ff_codec_id); |
| 30 | + if (!codec) |
| 31 | + return DecoderError::format(DecoderErrorCategory::NotImplemented, "Could not find FFmpeg decoder for codec {}", codec_id); |
| 32 | + |
| 33 | + codec_context = avcodec_alloc_context3(codec); |
| 34 | + if (!codec_context) |
| 35 | + return DecoderError::format(DecoderErrorCategory::Memory, "Failed to allocate FFmpeg codec context for codec {}", codec_id); |
| 36 | + |
| 37 | + codec_context->time_base = { 1, 1'000'000 }; |
| 38 | + codec_context->thread_count = static_cast<int>(min(Core::System::hardware_concurrency(), 4)); |
| 39 | + |
| 40 | + if (!codec_initialization_data.is_empty()) { |
| 41 | + if (codec_initialization_data.size() > NumericLimits<int>::max()) |
| 42 | + return DecoderError::corrupted("Codec initialization data is too large"sv); |
| 43 | + |
| 44 | + codec_context->extradata = static_cast<u8*>(av_malloc(codec_initialization_data.size() + AV_INPUT_BUFFER_PADDING_SIZE)); |
| 45 | + if (!codec_context->extradata) |
| 46 | + return DecoderError::with_description(DecoderErrorCategory::Memory, "Failed to allocate codec initialization data buffer for FFmpeg codec"sv); |
| 47 | + |
| 48 | + memcpy(codec_context->extradata, codec_initialization_data.data(), codec_initialization_data.size()); |
| 49 | + codec_context->extradata_size = static_cast<int>(codec_initialization_data.size()); |
| 50 | + } |
| 51 | + |
| 52 | + if (avcodec_open2(codec_context, codec, nullptr) < 0) |
| 53 | + return DecoderError::format(DecoderErrorCategory::Unknown, "Unknown error occurred when opening FFmpeg codec {}", codec_id); |
| 54 | + |
| 55 | + packet = av_packet_alloc(); |
| 56 | + if (!packet) |
| 57 | + return DecoderError::with_description(DecoderErrorCategory::Memory, "Failed to allocate FFmpeg packet"sv); |
| 58 | + |
| 59 | + frame = av_frame_alloc(); |
| 60 | + if (!frame) |
| 61 | + return DecoderError::with_description(DecoderErrorCategory::Memory, "Failed to allocate FFmpeg frame"sv); |
| 62 | + |
| 63 | + memory_guard.disarm(); |
| 64 | + return DECODER_TRY_ALLOC(try_make<FFmpegAudioDecoder>(codec_context, packet, frame)); |
| 65 | +} |
| 66 | + |
| 67 | +FFmpegAudioDecoder::FFmpegAudioDecoder(AVCodecContext* codec_context, AVPacket* packet, AVFrame* frame) |
| 68 | + : m_codec_context(codec_context) |
| 69 | + , m_packet(packet) |
| 70 | + , m_frame(frame) |
| 71 | +{ |
| 72 | +} |
| 73 | + |
| 74 | +FFmpegAudioDecoder::~FFmpegAudioDecoder() |
| 75 | +{ |
| 76 | + av_packet_free(&m_packet); |
| 77 | + av_frame_free(&m_frame); |
| 78 | + avcodec_free_context(&m_codec_context); |
| 79 | +} |
| 80 | + |
| 81 | +DecoderErrorOr<void> FFmpegAudioDecoder::receive_coded_data(AK::Duration timestamp, ReadonlyBytes coded_data) |
| 82 | +{ |
| 83 | + VERIFY(coded_data.size() < NumericLimits<int>::max()); |
| 84 | + |
| 85 | + m_packet->data = const_cast<u8*>(coded_data.data()); |
| 86 | + m_packet->size = static_cast<int>(coded_data.size()); |
| 87 | + m_packet->pts = timestamp.to_microseconds(); |
| 88 | + m_packet->dts = m_packet->pts; |
| 89 | + |
| 90 | + auto result = avcodec_send_packet(m_codec_context, m_packet); |
| 91 | + switch (result) { |
| 92 | + case 0: |
| 93 | + return {}; |
| 94 | + case AVERROR(EAGAIN): |
| 95 | + return DecoderError::with_description(DecoderErrorCategory::NeedsMoreInput, "FFmpeg decoder cannot decode any more data until frames have been retrieved"sv); |
| 96 | + case AVERROR_EOF: |
| 97 | + return DecoderError::with_description(DecoderErrorCategory::EndOfStream, "FFmpeg decoder has been flushed"sv); |
| 98 | + case AVERROR(EINVAL): |
| 99 | + return DecoderError::with_description(DecoderErrorCategory::Invalid, "FFmpeg codec has not been opened"sv); |
| 100 | + case AVERROR(ENOMEM): |
| 101 | + return DecoderError::with_description(DecoderErrorCategory::Memory, "FFmpeg codec ran out of internal memory"sv); |
| 102 | + default: |
| 103 | + return DecoderError::with_description(DecoderErrorCategory::Corrupted, "FFmpeg codec reports that the data is corrupted"sv); |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +template<typename T> |
| 108 | +static float float_sample_from_frame_data(u8** data, size_t plane, size_t index); |
| 109 | + |
| 110 | +template<> |
| 111 | +float float_sample_from_frame_data<u8>(u8** data, size_t plane, size_t index) |
| 112 | +{ |
| 113 | + return static_cast<float>(data[plane][index] - 127) / 255; |
| 114 | +} |
| 115 | + |
| 116 | +template<typename T> |
| 117 | +requires(IsSigned<T>) |
| 118 | +static float float_sample_from_frame_data(u8** data, size_t plane, size_t index) |
| 119 | +{ |
| 120 | + auto* pointer = reinterpret_cast<T*>(data[plane]); |
| 121 | + return pointer[index] / static_cast<float>(NumericLimits<T>::max()); |
| 122 | +} |
| 123 | + |
| 124 | +template<typename T> |
| 125 | +requires(IsFloatingPoint<T>) |
| 126 | +static float float_sample_from_frame_data(u8** data, size_t plane, size_t index) |
| 127 | +{ |
| 128 | + auto* pointer = reinterpret_cast<T*>(data[plane]); |
| 129 | + return pointer[index]; |
| 130 | +} |
| 131 | + |
| 132 | +DecoderErrorOr<void> FFmpegAudioDecoder::write_next_block(AudioBlock& block) |
| 133 | +{ |
| 134 | + auto result = avcodec_receive_frame(m_codec_context, m_frame); |
| 135 | + |
| 136 | + switch (result) { |
| 137 | + case 0: { |
| 138 | + auto timestamp = AK::Duration::from_microseconds(m_frame->pts); |
| 139 | + |
| 140 | + if (m_frame->ch_layout.nb_channels > 2) |
| 141 | + return DecoderError::not_implemented(); |
| 142 | + |
| 143 | + VERIFY(m_frame->sample_rate > 0); |
| 144 | + VERIFY(m_frame->ch_layout.nb_channels > 0); |
| 145 | + |
| 146 | + block.emplace(m_frame->sample_rate, m_frame->ch_layout.nb_channels, timestamp, [&](AudioBlock::Data& data) { |
| 147 | + auto format = static_cast<AVSampleFormat>(m_frame->format); |
| 148 | + auto is_planar = av_sample_fmt_is_planar(format) != 0; |
| 149 | + auto planar_format = av_get_planar_sample_fmt(format); |
| 150 | + |
| 151 | + VERIFY(m_frame->nb_samples >= 0); |
| 152 | + auto sample_count = static_cast<size_t>(m_frame->nb_samples); |
| 153 | + auto channel_count = static_cast<size_t>(m_frame->ch_layout.nb_channels); |
| 154 | + auto count = sample_count * channel_count; |
| 155 | + data = MUST(AudioBlock::Data::create(count)); |
| 156 | + |
| 157 | + auto sample_size = [&] { |
| 158 | + switch (planar_format) { |
| 159 | + case AV_SAMPLE_FMT_U8P: |
| 160 | + return sizeof(u8); |
| 161 | + case AV_SAMPLE_FMT_S16P: |
| 162 | + return sizeof(i16); |
| 163 | + case AV_SAMPLE_FMT_S32P: |
| 164 | + return sizeof(i32); |
| 165 | + case AV_SAMPLE_FMT_FLTP: |
| 166 | + return sizeof(float); |
| 167 | + case AV_SAMPLE_FMT_DBLP: |
| 168 | + return sizeof(double); |
| 169 | + case AV_SAMPLE_FMT_S64P: |
| 170 | + return sizeof(i64); |
| 171 | + default: |
| 172 | + VERIFY_NOT_REACHED(); |
| 173 | + } |
| 174 | + }(); |
| 175 | + |
| 176 | + VERIFY(m_frame->linesize[0] > 0); |
| 177 | + if (is_planar) |
| 178 | + VERIFY(static_cast<size_t>(m_frame->linesize[0]) >= sample_count * sample_size); |
| 179 | + else |
| 180 | + VERIFY(static_cast<size_t>(m_frame->linesize[0]) >= sample_count * channel_count * sample_size); |
| 181 | + |
| 182 | + for (size_t i = 0; i < count; i++) { |
| 183 | + size_t plane = 0; |
| 184 | + size_t index_in_plane = i; |
| 185 | + if (is_planar) { |
| 186 | + plane = i % channel_count; |
| 187 | + index_in_plane = i / channel_count; |
| 188 | + } |
| 189 | + |
| 190 | + auto float_sample = [&] { |
| 191 | + switch (planar_format) { |
| 192 | + case AV_SAMPLE_FMT_U8P: |
| 193 | + return float_sample_from_frame_data<u8>(m_frame->extended_data, plane, index_in_plane); |
| 194 | + case AV_SAMPLE_FMT_S16P: |
| 195 | + return float_sample_from_frame_data<i16>(m_frame->extended_data, plane, index_in_plane); |
| 196 | + case AV_SAMPLE_FMT_S32P: |
| 197 | + return float_sample_from_frame_data<i32>(m_frame->extended_data, plane, index_in_plane); |
| 198 | + case AV_SAMPLE_FMT_FLTP: |
| 199 | + return float_sample_from_frame_data<float>(m_frame->extended_data, plane, index_in_plane); |
| 200 | + case AV_SAMPLE_FMT_DBLP: |
| 201 | + return float_sample_from_frame_data<double>(m_frame->extended_data, plane, index_in_plane); |
| 202 | + case AV_SAMPLE_FMT_S64P: |
| 203 | + return float_sample_from_frame_data<i64>(m_frame->extended_data, plane, index_in_plane); |
| 204 | + default: |
| 205 | + VERIFY_NOT_REACHED(); |
| 206 | + } |
| 207 | + }(); |
| 208 | + data[i] = float_sample; |
| 209 | + } |
| 210 | + }); |
| 211 | + |
| 212 | + return {}; |
| 213 | + } |
| 214 | + case AVERROR(EAGAIN): |
| 215 | + return DecoderError::with_description(DecoderErrorCategory::NeedsMoreInput, "FFmpeg decoder has no frames available, send more input"sv); |
| 216 | + case AVERROR_EOF: |
| 217 | + return DecoderError::with_description(DecoderErrorCategory::EndOfStream, "FFmpeg decoder has been flushed"sv); |
| 218 | + case AVERROR(EINVAL): |
| 219 | + return DecoderError::with_description(DecoderErrorCategory::Invalid, "FFmpeg codec has not been opened"sv); |
| 220 | + default: |
| 221 | + return DecoderError::format(DecoderErrorCategory::Unknown, "FFmpeg codec encountered an unexpected error retrieving frames with code {:x}", result); |
| 222 | + } |
| 223 | +} |
| 224 | + |
| 225 | +void FFmpegAudioDecoder::flush() |
| 226 | +{ |
| 227 | + avcodec_flush_buffers(m_codec_context); |
| 228 | +} |
| 229 | + |
| 230 | +} |
0 commit comments