blob: e97a6a1b3a421214f7a48b8a16c2117119ec0cc2 [file] [log] [blame]
// 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_decoder_base.h"
#include <lib/async/cpp/task.h>
#include <trace/event.h>
#include "src/lib/fxl/logging.h"
#include "src/media/playback/mediaplayer/ffmpeg/av_codec_context.h"
#include "src/media/playback/mediaplayer/graph/formatting.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::kNoPts;
}
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