| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/media/playback/mediaplayer/ffmpeg/ffmpeg_audio_decoder.h" |
| |
| #include "lib/media/cpp/timeline_rate.h" |
| #include "src/lib/syslog/cpp/logger.h" |
| |
| namespace media_player { |
| |
| // static |
| std::shared_ptr<Processor> FfmpegAudioDecoder::Create(AvCodecContextPtr av_codec_context) { |
| return std::make_shared<FfmpegAudioDecoder>(std::move(av_codec_context)); |
| } |
| |
| FfmpegAudioDecoder::FfmpegAudioDecoder(AvCodecContextPtr av_codec_context) |
| : FfmpegDecoderBase(std::move(av_codec_context)) { |
| FX_DCHECK(context()); |
| FX_DCHECK(context()->channels > 0); |
| |
| std::unique_ptr<StreamType> stream_type = output_stream_type(); |
| FX_DCHECK(stream_type); |
| FX_DCHECK(stream_type->audio()); |
| set_pts_rate(media::TimelineRate(stream_type->audio()->frames_per_second(), 1)); |
| |
| stream_type_ = std::move(stream_type); |
| |
| if (av_sample_fmt_is_planar(context()->sample_fmt)) { |
| // Prepare for interleaving. |
| lpcm_util_ = LpcmUtil::Create(*stream_type_->audio()); |
| copy_or_interleave_ = true; |
| } |
| |
| // Codec-specific code goes here. |
| switch (context()->codec_id) { |
| case AV_CODEC_ID_OPUS: |
| // The opus decoder allocates buffers six times as large as the resulting payload. We need |
| // to copy the payloads, so we don't use up all the buffer space available to the renderer. |
| copy_or_interleave_ = true; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| FfmpegAudioDecoder::~FfmpegAudioDecoder() {} |
| |
| void FfmpegAudioDecoder::ConfigureConnectors() { |
| ConfigureInputToUseLocalMemory(0, 2); |
| // TODO(dalesat): Real numbers here. How big are packets? |
| // We're OK for now, because the audio renderer asks for a single VMO that's |
| // big enough to handle any packet we want to produce. |
| ConfigureOutputToUseLocalMemory(0, 1, 1); |
| } |
| |
| void FfmpegAudioDecoder::OnNewInputPacket(const PacketPtr& packet) { |
| if (next_pts() == Packet::kNoPts) { |
| set_next_pts(packet->GetPts(pts_rate())); |
| } |
| |
| context()->reordered_opaque = packet->discontinuity() ? 1 : 0; |
| context()->pkt_timebase.num = pts_rate().reference_delta(); |
| context()->pkt_timebase.den = pts_rate().subject_delta(); |
| } |
| |
| int FfmpegAudioDecoder::BuildAVFrame(const AVCodecContext& av_codec_context, AVFrame* av_frame) { |
| FX_DCHECK(av_frame); |
| |
| AVSampleFormat av_sample_format = static_cast<AVSampleFormat>(av_frame->format); |
| |
| int buffer_size = av_samples_get_buffer_size(&av_frame->linesize[0], av_codec_context.channels, |
| av_frame->nb_samples, av_sample_format, |
| FfmpegAudioDecoder::kChannelAlign); |
| if (buffer_size < 0) { |
| FX_LOGS(WARNING) << "av_samples_get_buffer_size failed"; |
| return buffer_size; |
| } |
| |
| // Get the right payload buffer. If we need to copy or interleave later, we just get |
| // a buffer allocated using malloc. If not, we ask the stage for a buffer. |
| fbl::RefPtr<PayloadBuffer> buffer = copy_or_interleave_ |
| ? PayloadBuffer::CreateWithMalloc(buffer_size) |
| : AllocatePayloadBuffer(buffer_size); |
| |
| if (!buffer) { |
| // TODO(dalesat): Record/report packet drop. |
| return -1; |
| } |
| |
| // Check that the allocator has met the common alignment requirements and |
| // that those requirements are good enough for the decoder. |
| FX_DCHECK(PayloadBuffer::IsAligned(buffer->data())); |
| FX_DCHECK(PayloadBuffer::kByteAlignment >= kChannelAlign); |
| |
| if (!av_sample_fmt_is_planar(av_sample_format)) { |
| // Samples are interleaved. There's just one buffer. |
| av_frame->data[0] = reinterpret_cast<uint8_t*>(buffer->data()); |
| } else { |
| // Samples are not interleaved. There's one buffer per channel. |
| int channels = av_codec_context.channels; |
| int bytes_per_channel = buffer_size / channels; |
| uint8_t* channel_buffer = reinterpret_cast<uint8_t*>(buffer->data()); |
| |
| FX_DCHECK(buffer || bytes_per_channel == 0); |
| |
| if (channels <= AV_NUM_DATA_POINTERS) { |
| // The buffer pointers will fit in av_frame->data. |
| FX_DCHECK(av_frame->extended_data == av_frame->data); |
| for (int channel = 0; channel < channels; ++channel) { |
| av_frame->data[channel] = channel_buffer; |
| channel_buffer += bytes_per_channel; |
| } |
| } else { |
| // Too many channels for av_frame->data. We have to use |
| // av_frame->extended_data |
| av_frame->extended_data = |
| static_cast<uint8_t**>(av_malloc(channels * sizeof(*av_frame->extended_data))); |
| |
| // The first AV_NUM_DATA_POINTERS go in both data and extended_data. |
| int channel = 0; |
| for (; channel < AV_NUM_DATA_POINTERS; ++channel) { |
| av_frame->extended_data[channel] = av_frame->data[channel] = channel_buffer; |
| channel_buffer += bytes_per_channel; |
| } |
| |
| // The rest go only in extended_data. |
| for (; channel < channels; ++channel) { |
| av_frame->extended_data[channel] = channel_buffer; |
| channel_buffer += bytes_per_channel; |
| } |
| } |
| } |
| |
| av_frame->buf[0] = CreateAVBuffer(std::move(buffer)); |
| av_frame->reordered_opaque = av_codec_context.reordered_opaque; |
| |
| return 0; |
| } |
| |
| PacketPtr FfmpegAudioDecoder::CreateOutputPacket(const AVFrame& av_frame, |
| fbl::RefPtr<PayloadBuffer> payload_buffer) { |
| FX_DCHECK(av_frame.buf[0]); |
| FX_DCHECK(payload_buffer); |
| |
| // We infer the PTS for a packet based on the assumption that the decoder |
| // produces an uninterrupted stream of frames. The PTS value in av_frame is |
| // often bogus, and we get bad results if we try to use it. This approach is |
| // consistent with the way Chromium deals with the ffmpeg audio decoders. |
| int64_t pts = next_pts(); |
| |
| if (pts != Packet::kNoPts) { |
| set_next_pts(pts + av_frame.nb_samples); |
| } |
| |
| FX_DCHECK(stream_type_); |
| FX_DCHECK(stream_type_->audio()); |
| |
| uint64_t payload_size = stream_type_->audio()->min_buffer_size(av_frame.nb_samples); |
| |
| if (copy_or_interleave_) { |
| // We need to copy or interleave. The original frames are in |
| // |payload_buffer|, which was allocated from system memory. That buffer |
| // will get released later in ReleaseBufferForAvFrame. We need a new |
| // buffer for the output payload, which we get from the stage. |
| auto new_payload_buffer = AllocatePayloadBuffer(payload_size); |
| if (!new_payload_buffer) { |
| // TODO(dalesat): Record/report packet drop. |
| return nullptr; |
| } |
| |
| if (lpcm_util_) { |
| lpcm_util_->Interleave(payload_buffer->data(), |
| av_frame.linesize[0] * stream_type_->audio()->channels(), |
| new_payload_buffer->data(), av_frame.nb_samples); |
| } else { |
| memcpy(new_payload_buffer->data(), payload_buffer->data(), payload_size); |
| } |
| |
| // |new_payload_buffer| is the buffer we want to attach to the |Packet|. |
| // This assignment drops the reference to the original |payload_buffer|, so |
| // it will be recycled once the |AVBuffer| is released. |
| payload_buffer = std::move(new_payload_buffer); |
| } |
| |
| // Create the output packet. We set the discontinuity bit on the packet if |
| // the corresponding input packet had one. |
| return Packet::Create(pts, pts_rate(), |
| false, // Not a keyframe |
| av_frame.reordered_opaque != 0, // discontinuity |
| false, // Not end-of-stream. The base class handles end-of-stream. |
| payload_size, std::move(payload_buffer)); |
| } |
| |
| const char* FfmpegAudioDecoder::label() const { return "audio_decoder"; } |
| |
| } // namespace media_player |