| // 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 "garnet/bin/mediaplayer/ffmpeg/ffmpeg_decoder_base.h" |
| |
| #include <lib/async/cpp/task.h> |
| #include <trace/event.h> |
| #include "garnet/bin/mediaplayer/ffmpeg/av_codec_context.h" |
| #include "garnet/bin/mediaplayer/graph/formatting.h" |
| #include "lib/fxl/logging.h" |
| extern "C" { |
| #include "libavutil/buffer_internal.h" |
| } |
| |
| namespace media_player { |
| namespace { |
| |
| // Creates an opaque reference to an object from an |fbl::RefPtr|. The original |
| // pointer is 'copied' in the sense that a refcount is added for the new opaque |
| // reference. |
| template <typename T> |
| void* CopyRefPtrToOpaque(fbl::RefPtr<T> t) { |
| FXL_DCHECK(t); |
| |
| // Increment the refcount to account for the opaque reference we're returning. |
| t->AddRef(); |
| return t.get(); |
| } |
| |
| // Copies an opaque reference created with |CopyRefPtrToOpaque| to create a |
| // new |fbl::RefPtr|. The opaque reference is 'copied' in the sense that a |
| // refcount is added for the new |fbl::RefPtr|. |
| template <typename T> |
| fbl::RefPtr<T> CopyOpaqueRefPtr(void* opaque) { |
| T* t_raw_ptr = reinterpret_cast<T*>(opaque); |
| |
| // Increment the refcount to account for the |fbl::RefPtr| we're about to |
| // create. |MakeRefPtrNoAdopt| doesn't increment the refcount. |
| t_raw_ptr->AddRef(); |
| return fbl::internal::MakeRefPtrNoAdopt(t_raw_ptr); |
| } |
| |
| // Releases an opaque reference created with |CopyRefPtrToOpaque|. |opaque| is |
| // passed by value, so the caller must be careful not to use |opaque| as an |
| // opaque RefPtr after calling this function. |
| template <typename T> |
| void ReleaseOpaqueRefPtr(void* opaque) { |
| // |MakeRefPtrNoAdopt| doesn't increment the refcount. When the |fbl::RefPtr| |
| // we've created here is dropped, the refcount in decremented, and the |T| |
| // may be deleted/recycled. |
| fbl::internal::MakeRefPtrNoAdopt(reinterpret_cast<T*>(opaque)); |
| } |
| |
| } // namespace |
| |
| FfmpegDecoderBase::FfmpegDecoderBase(AvCodecContextPtr av_codec_context) |
| : av_codec_context_(std::move(av_codec_context)), |
| av_frame_ptr_(ffmpeg::AvFrame::Create()) { |
| FXL_DCHECK(av_codec_context_); |
| |
| av_codec_context_->opaque = this; |
| av_codec_context_->get_buffer2 = AllocateBufferForAvFrame; |
| av_codec_context_->refcounted_frames = 1; |
| } |
| |
| FfmpegDecoderBase::~FfmpegDecoderBase() {} |
| |
| std::unique_ptr<StreamType> FfmpegDecoderBase::output_stream_type() const { |
| return AvCodecContext::GetStreamType(*av_codec_context_); |
| } |
| |
| void FfmpegDecoderBase::Flush() { |
| FXL_DCHECK(is_worker_thread()); |
| avcodec_flush_buffers(av_codec_context_.get()); |
| next_pts_ = Packet::kUnknownPts; |
| } |
| |
| bool FfmpegDecoderBase::TransformPacket(const PacketPtr& input, bool new_input, |
| PacketPtr* output) { |
| FXL_DCHECK(is_worker_thread()); |
| FXL_DCHECK(input); |
| FXL_DCHECK(output); |
| |
| TRACE_DURATION( |
| "motown", "DecodePacket", "type", |
| (av_codec_context_->codec_type == AVMEDIA_TYPE_VIDEO ? "video" |
| : "audio")); |
| |
| *output = nullptr; |
| |
| if (new_input) { |
| if (input->size() == 0 && !input->end_of_stream()) { |
| // This packet isn't end-of-stream, but it has size zero. The underlying |
| // decoder interprets an empty input packet as end-of-stream, so we |
| // we refrain from decoding this packet and return true to indicate we're |
| // done with it. |
| // |
| // The underlying decoder gets its end-of-stream indication in one of |
| // two ways: |
| // 1) If the end-of-stream packet is empty, it will get past this check |
| // and be submitted to the decoder, indicating end-of-stream. |
| // 2) If the end-of-stream packet is not empty, we let it through and |
| // follow it with an empty end-of-stream packet that we create for |
| // that purpose. |
| return true; |
| } |
| |
| OnNewInputPacket(input); |
| |
| // Send the packet to the ffmpeg decoder. If it fails, return true to |
| // indicate we're done with the packet. |
| if (SendPacket(input) != 0) { |
| if (input->end_of_stream()) { |
| // The input packet was end-of-stream. We won't get called again before |
| // a flush, so make sure the output gets an end-of-stream packet. |
| *output = CreateEndOfStreamPacket(); |
| } |
| |
| return true; |
| } |
| } |
| |
| int result = |
| avcodec_receive_frame(av_codec_context_.get(), av_frame_ptr_.get()); |
| |
| switch (result) { |
| case 0: |
| // Succeeded, frame produced. We're not done with the input packet. |
| // |
| // We use |CopyOpaqueRefPtr| here to create a real |fbl:RefPtr| to the |
| // |PayloadBuffer| attached to the frame's |AVBuffer| in |CreateAVBuffer|. |
| *output = CreateOutputPacket(*av_frame_ptr_, |
| CopyOpaqueRefPtr<PayloadBuffer>( |
| av_frame_ptr_->buf[0]->buffer->opaque)); |
| |
| // Release the frame returned by |avcodec_receive_frame|. |
| av_frame_unref(av_frame_ptr_.get()); |
| return false; |
| |
| case AVERROR(EAGAIN): |
| // Succeeded, no frame produced. |
| if (input->end_of_stream() && input->size() != 0) { |
| // The input packet is an end-of-stream packet, and it has payload. The |
| // underlying decoder interprets an empty packet as end-of-stream, so |
| // we need to send it an empty packet. |
| if (SendPacket(CreateEndOfStreamPacket()) == 0) { |
| // |SendPacket| succeeded. We return false to indicate we're not done |
| // with the original end-of-stream packet. We'll get called again with |
| // the same end-of-stream packet and |new_input| set to false. That |
| // will continue until we've extracted all the output packets the |
| // decoder has to give us. Note that we won't end up here again, |
| // because |avcodec_receive_frame| will return either 0 or |
| // |AVERROR_EOF|, not |AVERROR(EAGAIN)|. |
| return false; |
| } |
| |
| // |SendPacket| failed. We return true to indicate we're done with the |
| // input packet. We also output an end-of-stream packet to terminate |
| // the output stream. |
| *output = CreateEndOfStreamPacket(); |
| } |
| |
| // Indicate we're done with the input packet. |
| return true; |
| |
| case AVERROR_EOF: |
| // Succeeded, no frame produced, end-of-stream sequence complete. |
| // Produce an end-of-stream packet. |
| FXL_DCHECK(input->end_of_stream()); |
| *output = CreateEndOfStreamPacket(); |
| return true; |
| |
| default: |
| FXL_DLOG(ERROR) << "avcodec_receive_frame failed " << result; |
| if (input->end_of_stream()) { |
| // The input packet was end-of-stream. We won't get called again before |
| // a flush, so make sure the output gets an end-of-stream packet. |
| *output = CreateEndOfStreamPacket(); |
| } |
| |
| return true; |
| } |
| } |
| |
| int FfmpegDecoderBase::SendPacket(const PacketPtr& input) { |
| FXL_DCHECK(input); |
| |
| AVPacket av_packet; |
| av_init_packet(&av_packet); |
| av_packet.data = reinterpret_cast<uint8_t*>(input->payload()); |
| av_packet.size = input->size(); |
| av_packet.pts = input->pts(); |
| if (input->keyframe()) { |
| av_packet.flags |= AV_PKT_FLAG_KEY; |
| } |
| |
| int result = avcodec_send_packet(av_codec_context_.get(), &av_packet); |
| |
| if (result != 0) { |
| FXL_DLOG(ERROR) << "avcodec_send_packet failed " << result; |
| } |
| |
| return result; |
| } |
| |
| void FfmpegDecoderBase::OnNewInputPacket(const PacketPtr& packet) {} |
| |
| AVBufferRef* FfmpegDecoderBase::CreateAVBuffer( |
| fbl::RefPtr<PayloadBuffer> payload_buffer) { |
| FXL_DCHECK(payload_buffer); |
| FXL_DCHECK(payload_buffer->size() <= |
| static_cast<uint64_t>(std::numeric_limits<int>::max())); |
| return av_buffer_create(reinterpret_cast<uint8_t*>(payload_buffer->data()), |
| static_cast<int>(payload_buffer->size()), |
| ReleaseBufferForAvFrame, |
| CopyRefPtrToOpaque(payload_buffer), |
| /* flags */ 0); |
| } |
| |
| // static |
| int FfmpegDecoderBase::AllocateBufferForAvFrame( |
| AVCodecContext* av_codec_context, AVFrame* av_frame, int flags) { |
| // It's important to use av_codec_context here rather than context(), |
| // because av_codec_context is different for different threads when we're |
| // decoding on multiple threads. Be sure to avoid using self->context() or |
| // self->av_codec_context_. |
| |
| // AV_CODEC_CAP_DR1 is required in order to do allocation this way. |
| FXL_DCHECK(av_codec_context->codec->capabilities & AV_CODEC_CAP_DR1); |
| |
| FfmpegDecoderBase* self = |
| reinterpret_cast<FfmpegDecoderBase*>(av_codec_context->opaque); |
| FXL_DCHECK(self); |
| |
| return self->BuildAVFrame(*av_codec_context, av_frame); |
| } |
| |
| // static |
| void FfmpegDecoderBase::ReleaseBufferForAvFrame(void* opaque, uint8_t* buffer) { |
| FXL_DCHECK(opaque); |
| FXL_DCHECK(buffer); |
| FXL_DCHECK(buffer == CopyOpaqueRefPtr<PayloadBuffer>(opaque)->data()); |
| |
| ReleaseOpaqueRefPtr<PayloadBuffer>(opaque); |
| } |
| |
| PacketPtr FfmpegDecoderBase::CreateEndOfStreamPacket() { |
| return Packet::CreateEndOfStream(next_pts_, pts_rate_); |
| } |
| |
| void FfmpegDecoderBase::Dump(std::ostream& os) const { |
| SoftwareDecoder::Dump(os); |
| |
| os << fostr::Indent; |
| os << fostr::NewLine << "next pts: " << AsNs(next_pts_) << "@" |
| << pts_rate_; |
| os << fostr::Outdent; |
| } |
| |
| } // namespace media_player |