| // Copyright 2019 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 "avcodec_context.h" |
| |
| #include <map> |
| #include <string> |
| |
| #include <lib/media/codec_impl/codec_buffer.h> |
| |
| namespace { |
| |
| // TODO(turnage): Add VP9, and more. |
| static const std::map<std::string, AVCodecID> codec_ids = { |
| {"video/h264", AV_CODEC_ID_H264}}; |
| |
| static inline constexpr uint32_t make_fourcc(uint8_t a, uint8_t b, uint8_t c, |
| uint8_t d) { |
| return (static_cast<uint32_t>(d) << 24) | (static_cast<uint32_t>(c) << 16) | |
| (static_cast<uint32_t>(b) << 8) | static_cast<uint32_t>(a); |
| } |
| |
| } // namespace |
| |
| std::optional<std::unique_ptr<AvCodecContext>> AvCodecContext::CreateDecoder( |
| const fuchsia::media::FormatDetails& format_details, |
| GetBufferCallback get_buffer_callback) { |
| avcodec_register_all(); |
| auto codec_id = codec_ids.find(format_details.mime_type); |
| if (codec_id == codec_ids.end()) { |
| return std::nullopt; |
| } |
| |
| AVCodec* codec = avcodec_find_decoder(codec_id->second); |
| ZX_DEBUG_ASSERT(codec); |
| ZX_DEBUG_ASSERT(av_codec_is_decoder(codec)); |
| auto avcodec_context = |
| std::unique_ptr<AVCodecContext, fit::function<void(AVCodecContext*)>>( |
| avcodec_alloc_context3(codec), [](AVCodecContext* avcodec_context) { |
| avcodec_free_context(&avcodec_context); |
| }); |
| ZX_ASSERT(avcodec_context); |
| |
| // This flag must be set in case our packets come on NAL boundaries |
| // and not just frame boundaries. |
| avcodec_context->flags2 |= AV_CODEC_FLAG2_CHUNKS; |
| |
| // This flag is required to override get_buffer2. |
| ZX_ASSERT(avcodec_context->codec->capabilities & AV_CODEC_CAP_DR1); |
| |
| avcodec_context->get_buffer2 = AvCodecContext::GetBufferCallbackRouter; |
| |
| std::unique_ptr<AvCodecContext> decoder(new AvCodecContext( |
| std::move(avcodec_context), std::move(get_buffer_callback))); |
| |
| const std::vector<uint8_t>& oob = format_details.oob_bytes.get(); |
| ZX_DEBUG_ASSERT(oob.empty() || format_details.oob_bytes); |
| if (!oob.empty()) { |
| // Freed in AVCodecContext deleter in avcodec_free. |
| auto* extradata = reinterpret_cast<uint8_t*>(av_malloc(oob.size())); |
| ZX_ASSERT(extradata); |
| std::memcpy(extradata, oob.data(), oob.size()); |
| decoder->avcodec_context_->extradata = extradata; |
| decoder->avcodec_context_->extradata_size = oob.size(); |
| } |
| |
| int open_error = |
| avcodec_open2(decoder->avcodec_context_.get(), codec, nullptr); |
| ZX_ASSERT(!open_error); |
| ZX_DEBUG_ASSERT(avcodec_is_open(decoder->avcodec_context_.get())); |
| |
| return decoder; |
| } |
| |
| int AvCodecContext::SendPacket(const CodecPacket* codec_packet) { |
| ZX_DEBUG_ASSERT(codec_packet); |
| ZX_DEBUG_ASSERT(avcodec_context_); |
| ZX_DEBUG_ASSERT(avcodec_is_open(avcodec_context_.get())); |
| ZX_DEBUG_ASSERT(av_codec_is_decoder(avcodec_context_->codec)); |
| ZX_DEBUG_ASSERT(codec_packet->has_start_offset()); |
| ZX_DEBUG_ASSERT(codec_packet->has_valid_length_bytes()); |
| ZX_DEBUG_ASSERT(codec_packet->buffer()); |
| |
| AVPacket packet; |
| av_init_packet(&packet); |
| packet.data = |
| codec_packet->buffer()->buffer_base() + codec_packet->start_offset(); |
| packet.size = codec_packet->valid_length_bytes(); |
| |
| if (codec_packet->has_timestamp_ish()) { |
| packet.pts = codec_packet->timestamp_ish(); |
| } |
| |
| return avcodec_send_packet(avcodec_context_.get(), &packet); |
| } |
| |
| std::pair<int, AvCodecContext::AVFramePtr> AvCodecContext::ReceiveFrame() { |
| ZX_DEBUG_ASSERT(avcodec_context_); |
| ZX_DEBUG_ASSERT(avcodec_is_open(avcodec_context_.get())); |
| ZX_DEBUG_ASSERT(av_codec_is_decoder(avcodec_context_->codec)); |
| |
| AVFramePtr frame(av_frame_alloc(), |
| [](AVFrame* frame) { av_frame_free(&frame); }); |
| // If we can't allocate a frame, abort this isolate process. |
| ZX_ASSERT(frame); |
| |
| int result_code = avcodec_receive_frame(avcodec_context_.get(), frame.get()); |
| if (result_code < 0) { |
| return {result_code, nullptr}; |
| } |
| |
| return {result_code, std::move(frame)}; |
| } |
| |
| int AvCodecContext::EndStream() { |
| ZX_DEBUG_ASSERT(avcodec_context_); |
| ZX_DEBUG_ASSERT(avcodec_is_open(avcodec_context_.get())); |
| ZX_DEBUG_ASSERT(av_codec_is_decoder(avcodec_context_->codec)); |
| return avcodec_send_packet(avcodec_context_.get(), nullptr); |
| } |
| |
| AvCodecContext::DecodedOutputInfo AvCodecContext::decoded_output_info( |
| AVFrame* frame) const { |
| ZX_DEBUG_ASSERT(avcodec_context_); |
| ZX_DEBUG_ASSERT(avcodec_is_open(avcodec_context_.get())); |
| ZX_DEBUG_ASSERT(av_codec_is_decoder(avcodec_context_->codec)); |
| // TODO(turnage): Accept 10 bit YUV formats. |
| ZX_DEBUG_ASSERT(frame->format == AV_PIX_FMT_YUV420P); |
| // We only implement right and bottom crops, not left or top crops. |
| ZX_ASSERT(frame->crop_left == 0); |
| ZX_ASSERT(frame->crop_top == 0); |
| |
| int linesizes[4]; |
| av_image_fill_linesizes(linesizes, static_cast<AVPixelFormat>(frame->format), |
| frame->width); |
| |
| fuchsia::media::VideoUncompressedFormat uncompressed_format; |
| uncompressed_format.fourcc = make_fourcc('Y', 'V', '1', '2'); |
| |
| uncompressed_format.primary_start_offset = 0; |
| uncompressed_format.primary_pixel_stride = 1; |
| uncompressed_format.primary_line_stride_bytes = linesizes[0]; |
| uncompressed_format.primary_width_pixels = frame->width; |
| uncompressed_format.primary_height_pixels = frame->height; |
| uncompressed_format.primary_display_width_pixels = |
| frame->width - frame->crop_right; |
| uncompressed_format.primary_display_height_pixels = |
| frame->height - frame->crop_bottom; |
| |
| // TODO(dustingreen): remove this field from the VideoUncompressedFormat or |
| // specify separately for primary / secondary. |
| uncompressed_format.planar = true; |
| uncompressed_format.swizzled = false; |
| |
| uncompressed_format.secondary_pixel_stride = 1; |
| uncompressed_format.secondary_width_pixels = frame->width / 2; |
| uncompressed_format.secondary_height_pixels = frame->height / 2; |
| uncompressed_format.secondary_line_stride_bytes = linesizes[1]; |
| uncompressed_format.secondary_start_offset = linesizes[0] * frame->height; |
| |
| uncompressed_format.tertiary_start_offset = |
| uncompressed_format.secondary_start_offset + |
| uncompressed_format.secondary_height_pixels * linesizes[1]; |
| |
| uncompressed_format.has_pixel_aspect_ratio = !!frame->sample_aspect_ratio.num; |
| if (uncompressed_format.has_pixel_aspect_ratio) { |
| uncompressed_format.pixel_aspect_ratio_width = |
| frame->sample_aspect_ratio.num; |
| uncompressed_format.pixel_aspect_ratio_height = |
| frame->sample_aspect_ratio.den; |
| } |
| |
| // TODO(dustingreen): Switching to FIDL table should make this not be |
| // required. |
| uncompressed_format.special_formats.set_temp_field_todo_remove(0); |
| |
| size_t buffer_bytes_needed = av_image_get_buffer_size( |
| static_cast<AVPixelFormat>(frame->format), frame->width, frame->height, |
| /*align=*/1); |
| |
| return {.format = std::move(uncompressed_format), |
| .buffer_bytes_needed = buffer_bytes_needed}; |
| } |
| |
| // static |
| int AvCodecContext::GetBufferCallbackRouter(AVCodecContext* avcodec_context, |
| AVFrame* frame, int flags) { |
| auto instance = reinterpret_cast<AvCodecContext*>(avcodec_context->opaque); |
| ZX_DEBUG_ASSERT(instance); |
| return instance->GetBufferHandler(avcodec_context, frame, flags); |
| } |
| |
| int AvCodecContext::GetBufferHandler(AVCodecContext* avcodec_context, |
| AVFrame* frame, int flags) { |
| ZX_DEBUG_ASSERT(avcodec_context_); |
| ZX_DEBUG_ASSERT(get_buffer_callback_); |
| ZX_DEBUG_ASSERT(frame->width); |
| |
| return get_buffer_callback_(decoded_output_info(frame), avcodec_context, |
| frame, flags); |
| } |
| |
| AvCodecContext::AvCodecContext( |
| std::unique_ptr<AVCodecContext, fit::function<void(AVCodecContext*)>> |
| avcodec_context, |
| GetBufferCallback get_buffer_callback) |
| : avcodec_context_(std::move(avcodec_context)), |
| get_buffer_callback_(std::move(get_buffer_callback)) { |
| ZX_DEBUG_ASSERT(avcodec_context_); |
| ZX_DEBUG_ASSERT(get_buffer_callback_); |
| |
| avcodec_context_->opaque = this; |
| } |