/***************************************************************************** * mediacodec.c: Video decoder module using the Android MediaCodec API ***************************************************************************** * Copyright (C) 2012 Martin Storsjo * * Authors: Martin Storsjo * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ /***************************************************************************** * Preamble *****************************************************************************/ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include "mediacodec.h" #include "../codec/hxxx_helper.h" #include #include #include "omxil_utils.h" #include "../../video_output/android/display.h" #define BLOCK_FLAG_CSD (0x01 << BLOCK_FLAG_PRIVATE_SHIFT) #define DECODE_FLAG_RESTART (0x01) #define DECODE_FLAG_DRAIN (0x02) /** * Callback called when a new block is processed from DecodeBlock. * It returns -1 in case of error, 0 if block should be dropped, 1 otherwise. */ typedef int (*dec_on_new_block_cb)(decoder_t *, block_t **); /** * Callback called when decoder is flushing. */ typedef void (*dec_on_flush_cb)(decoder_t *); /** * Callback called when DecodeBlock try to get an output buffer (pic or block). * It returns -1 in case of error, or the number of output buffer returned. */ typedef int (*dec_process_output_cb)(decoder_t *, mc_api_out *, picture_t **, block_t **); struct decoder_sys_t { mc_api api; /* Codec Specific Data buffer: sent in DecodeBlock after a start or a flush * with the BUFFER_FLAG_CODEC_CONFIG flag.*/ #define MAX_CSD_COUNT 3 block_t *pp_csd[MAX_CSD_COUNT]; size_t i_csd_count; size_t i_csd_send; bool b_has_format; int64_t i_preroll_end; int i_quirks; /* Specific Audio/Video callbacks */ dec_on_new_block_cb pf_on_new_block; dec_on_flush_cb pf_on_flush; dec_process_output_cb pf_process_output; vlc_mutex_t lock; vlc_thread_t out_thread; /* Cond used to signal the output thread */ vlc_cond_t cond; /* Cond used to signal the decoder thread */ vlc_cond_t dec_cond; /* Set to true by pf_flush to signal the output thread to flush */ bool b_flush_out; /* If true, the output thread will start to dequeue output pictures */ bool b_output_ready; /* If true, the first input block was successfully dequeued */ bool b_input_dequeued; bool b_aborted; bool b_drained; bool b_adaptive; int i_decode_flags; union { struct { void *p_surface, *p_jsurface; unsigned i_angle; unsigned i_input_width, i_input_height; unsigned int i_stride, i_slice_height; int i_pixel_format; struct hxxx_helper hh; /* stores the inflight picture for each output buffer or NULL */ picture_sys_t** pp_inflight_pictures; unsigned int i_inflight_pictures; timestamp_fifo_t *timestamp_fifo; int i_mpeg_dar_num, i_mpeg_dar_den; } video; struct { date_t i_end_date; int i_channels; bool b_extract; /* Some audio decoders need a valid channel count */ bool b_need_channels; int pi_extraction[AOUT_CHAN_MAX]; } audio; }; }; /***************************************************************************** * Local prototypes *****************************************************************************/ static int OpenDecoderJni(vlc_object_t *); static int OpenDecoderNdk(vlc_object_t *); static void CleanDecoder(decoder_t *); static void CloseDecoder(vlc_object_t *); static int Video_OnNewBlock(decoder_t *, block_t **); static int VideoHXXX_OnNewBlock(decoder_t *, block_t **); static int VideoMPEG2_OnNewBlock(decoder_t *, block_t **); static int VideoVC1_OnNewBlock(decoder_t *, block_t **); static void Video_OnFlush(decoder_t *); static int Video_ProcessOutput(decoder_t *, mc_api_out *, picture_t **, block_t **); static int DecodeBlock(decoder_t *, block_t *); static int Audio_OnNewBlock(decoder_t *, block_t **); static void Audio_OnFlush(decoder_t *); static int Audio_ProcessOutput(decoder_t *, mc_api_out *, picture_t **, block_t **); static void DecodeFlushLocked(decoder_t *); static void DecodeFlush(decoder_t *); static void StopMediaCodec(decoder_t *); static void *OutThread(void *); static void InvalidateAllPictures(decoder_t *); static void RemoveInflightPictures(decoder_t *); /***************************************************************************** * Module descriptor *****************************************************************************/ #define DIRECTRENDERING_TEXT "Android direct rendering" #define DIRECTRENDERING_LONGTEXT \ "Enable Android direct rendering using opaque buffers." #define MEDIACODEC_AUDIO_TEXT "Use MediaCodec for audio decoding" #define MEDIACODEC_AUDIO_LONGTEXT "Still experimental." #define MEDIACODEC_TUNNELEDPLAYBACK_TEXT "Use a tunneled surface for playback" #define CFG_PREFIX "mediacodec-" vlc_module_begin () set_description("Video decoder using Android MediaCodec via NDK") set_category(CAT_INPUT) set_subcategory(SUBCAT_INPUT_VCODEC) set_section(N_("Decoding"), NULL) set_capability("video decoder", 0) /* Only enabled via commandline arguments */ add_bool(CFG_PREFIX "dr", true, DIRECTRENDERING_TEXT, DIRECTRENDERING_LONGTEXT, true) add_bool(CFG_PREFIX "audio", false, MEDIACODEC_AUDIO_TEXT, MEDIACODEC_AUDIO_LONGTEXT, true) add_bool(CFG_PREFIX "tunneled-playback", false, MEDIACODEC_TUNNELEDPLAYBACK_TEXT, NULL, true) set_callbacks(OpenDecoderNdk, CloseDecoder) add_shortcut("mediacodec_ndk") add_submodule () set_capability("audio decoder", 0) set_callbacks(OpenDecoderNdk, CloseDecoder) add_shortcut("mediacodec_ndk") add_submodule () set_description("Video decoder using Android MediaCodec via JNI") set_capability("video decoder", 0) set_callbacks(OpenDecoderJni, CloseDecoder) add_shortcut("mediacodec_jni") add_submodule () set_capability("audio decoder", 0) set_callbacks(OpenDecoderJni, CloseDecoder) add_shortcut("mediacodec_jni") vlc_module_end () static void CSDFree(decoder_t *p_dec) { decoder_sys_t *p_sys = p_dec->p_sys; for (unsigned int i = 0; i < p_sys->i_csd_count; ++i) block_Release(p_sys->pp_csd[i]); p_sys->i_csd_count = 0; } /* Init the p_sys->p_csd that will be sent from DecodeBlock */ static void CSDInit(decoder_t *p_dec, block_t *p_blocks, size_t i_count) { decoder_sys_t *p_sys = p_dec->p_sys; assert(i_count <= MAX_CSD_COUNT); CSDFree(p_dec); for (size_t i = 0; i < i_count; ++i) { assert(p_blocks != NULL); p_sys->pp_csd[i] = p_blocks; p_sys->pp_csd[i]->i_flags = BLOCK_FLAG_CSD; p_blocks = p_blocks->p_next; p_sys->pp_csd[i]->p_next = NULL; } p_sys->i_csd_count = i_count; p_sys->i_csd_send = 0; } static int CSDDup(decoder_t *p_dec, const void *p_buf, size_t i_buf) { block_t *p_block = block_Alloc(i_buf); if (!p_block) return VLC_ENOMEM; memcpy(p_block->p_buffer, p_buf, i_buf); CSDInit(p_dec, p_block, 1); return VLC_SUCCESS; } static void HXXXInitSize(decoder_t *p_dec, bool *p_size_changed) { if (p_size_changed) { decoder_sys_t *p_sys = p_dec->p_sys; struct hxxx_helper *hh = &p_sys->video.hh; unsigned i_w, i_h, i_vw, i_vh; hxxx_helper_get_current_picture_size(hh, &i_w, &i_h, &i_vw, &i_vh); *p_size_changed = (i_w != p_sys->video.i_input_width || i_h != p_sys->video.i_input_height); p_sys->video.i_input_width = i_w; p_sys->video.i_input_height = i_h; /* fmt_out video size will be updated by mediacodec output callback */ } } /* Fill the p_sys->p_csd struct with H264 Parameter Sets */ static int H264SetCSD(decoder_t *p_dec, bool *p_size_changed) { decoder_sys_t *p_sys = p_dec->p_sys; struct hxxx_helper *hh = &p_sys->video.hh; assert(hh->h264.i_sps_count > 0 || hh->h264.i_pps_count > 0); block_t *p_spspps_blocks = h264_helper_get_annexb_config(hh); if (p_spspps_blocks != NULL) CSDInit(p_dec, p_spspps_blocks, 2); HXXXInitSize(p_dec, p_size_changed); return VLC_SUCCESS; } /* Fill the p_sys->p_csd struct with HEVC Parameter Sets */ static int HEVCSetCSD(decoder_t *p_dec, bool *p_size_changed) { decoder_sys_t *p_sys = p_dec->p_sys; struct hxxx_helper *hh = &p_sys->video.hh; assert(hh->hevc.i_vps_count > 0 || hh->hevc.i_sps_count > 0 || hh->hevc.i_pps_count > 0 ); block_t *p_xps_blocks = hevc_helper_get_annexb_config(hh); if (p_xps_blocks != NULL) { block_t *p_monolith = block_ChainGather(p_xps_blocks); if (p_monolith == NULL) { block_ChainRelease(p_xps_blocks); return VLC_ENOMEM; } CSDInit(p_dec, p_monolith, 1); } HXXXInitSize(p_dec, p_size_changed); return VLC_SUCCESS; } static int ParseVideoExtraH264(decoder_t *p_dec, uint8_t *p_extra, int i_extra) { decoder_sys_t *p_sys = p_dec->p_sys; struct hxxx_helper *hh = &p_sys->video.hh; int i_ret = hxxx_helper_set_extra(hh, p_extra, i_extra); if (i_ret != VLC_SUCCESS) return i_ret; assert(hh->pf_process_block != NULL); if (p_sys->api.i_quirks & MC_API_VIDEO_QUIRKS_ADAPTIVE) p_sys->b_adaptive = true; p_sys->pf_on_new_block = VideoHXXX_OnNewBlock; if (hh->h264.i_sps_count > 0 || hh->h264.i_pps_count > 0) return H264SetCSD(p_dec, NULL); return VLC_SUCCESS; } static int ParseVideoExtraHEVC(decoder_t *p_dec, uint8_t *p_extra, int i_extra) { decoder_sys_t *p_sys = p_dec->p_sys; struct hxxx_helper *hh = &p_sys->video.hh; int i_ret = hxxx_helper_set_extra(hh, p_extra, i_extra); if (i_ret != VLC_SUCCESS) return i_ret; assert(hh->pf_process_block != NULL); if (p_sys->api.i_quirks & MC_API_VIDEO_QUIRKS_ADAPTIVE) p_sys->b_adaptive = true; p_sys->pf_on_new_block = VideoHXXX_OnNewBlock; if (hh->hevc.i_vps_count > 0 || hh->hevc.i_sps_count > 0 || hh->hevc.i_pps_count > 0 ) return HEVCSetCSD(p_dec, NULL); return VLC_SUCCESS; } static int ParseVideoExtraVc1(decoder_t *p_dec, uint8_t *p_extra, int i_extra) { int offset = 0; if (i_extra < 4) return VLC_EGENERIC; /* Initialisation data starts with : 0x00 0x00 0x01 0x0f */ /* Skipping unnecessary data */ static const uint8_t vc1_start_code[4] = {0x00, 0x00, 0x01, 0x0f}; for (; offset < i_extra - 4 ; ++offset) { if (!memcmp(&p_extra[offset], vc1_start_code, 4)) break; } /* Could not find the sequence header start code */ if (offset >= i_extra - 4) return VLC_EGENERIC; p_dec->p_sys->pf_on_new_block = VideoVC1_OnNewBlock; return CSDDup(p_dec, p_extra + offset, i_extra - offset); } static int ParseVideoExtraWmv3(decoder_t *p_dec, uint8_t *p_extra, int i_extra) { /* WMV3 initialisation data : * 8 fixed bytes * 4 extradata bytes * 4 height bytes (little endian) * 4 width bytes (little endian) * 16 fixed bytes */ if (i_extra < 4) return VLC_EGENERIC; uint8_t p_data[36] = { 0x8e, 0x01, 0x00, 0xc5, /* Fixed bytes values */ 0x04, 0x00, 0x00, 0x00, /* Same */ 0x00, 0x00, 0x00, 0x00, /* extradata emplacement */ 0x00, 0x00, 0x00, 0x00, /* height emplacement (little endian) */ 0x00, 0x00, 0x00, 0x00, /* width emplacement (little endian) */ 0x0c, 0x00, 0x00, 0x00, /* Fixed byte pattern */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /* Adding extradata */ memcpy(&p_data[8], p_extra, 4); /* Adding height and width, little endian */ SetDWLE(&(p_data[12]), p_dec->fmt_in.video.i_height); SetDWLE(&(p_data[16]), p_dec->fmt_in.video.i_width); return CSDDup(p_dec, p_data, sizeof(p_data)); } static int ParseExtra(decoder_t *p_dec) { decoder_sys_t *p_sys = p_dec->p_sys; uint8_t *p_extra = p_dec->fmt_in.p_extra; int i_extra = p_dec->fmt_in.i_extra; switch (p_dec->fmt_in.i_codec) { case VLC_CODEC_H264: return ParseVideoExtraH264(p_dec, p_extra, i_extra); case VLC_CODEC_HEVC: return ParseVideoExtraHEVC(p_dec, p_extra, i_extra); case VLC_CODEC_WMV3: return ParseVideoExtraWmv3(p_dec, p_extra, i_extra); case VLC_CODEC_VC1: return ParseVideoExtraVc1(p_dec, p_extra, i_extra); case VLC_CODEC_MP4V: if (!i_extra && p_sys->api.i_quirks & MC_API_VIDEO_QUIRKS_ADAPTIVE) p_sys->b_adaptive = true; break; case VLC_CODEC_MPGV: case VLC_CODEC_MP2V: p_dec->p_sys->pf_on_new_block = VideoMPEG2_OnNewBlock; break; } /* Set default CSD */ if (p_dec->fmt_in.i_extra) return CSDDup(p_dec, p_dec->fmt_in.p_extra, p_dec->fmt_in.i_extra); else return VLC_SUCCESS; } static int UpdateVout(decoder_t *p_dec) { decoder_sys_t *p_sys = p_dec->p_sys; if ((p_dec->fmt_in.i_codec == VLC_CODEC_MPGV || p_dec->fmt_in.i_codec == VLC_CODEC_MP2V) && (p_sys->video.i_mpeg_dar_num * p_sys->video.i_mpeg_dar_den != 0)) { p_dec->fmt_out.video.i_sar_num = p_sys->video.i_mpeg_dar_num * p_dec->fmt_out.video.i_height; p_dec->fmt_out.video.i_sar_den = p_sys->video.i_mpeg_dar_den * p_dec->fmt_out.video.i_width; } /* If MediaCodec can handle the rotation, reset the orientation to * Normal in order to ask the vout not to rotate. */ p_dec->fmt_out.video.orientation = p_dec->fmt_in.video.orientation; if (p_sys->video.i_angle != 0) { assert(p_dec->fmt_out.i_codec == VLC_CODEC_ANDROID_OPAQUE); video_format_TransformTo(&p_dec->fmt_out.video, ORIENT_NORMAL); } if (decoder_UpdateVideoFormat(p_dec) != 0) return VLC_EGENERIC; if (p_dec->fmt_out.i_codec != VLC_CODEC_ANDROID_OPAQUE) return VLC_SUCCESS; /* Direct rendering: get the surface attached to the VOUT */ picture_t *p_dummy_hwpic = decoder_NewPicture(p_dec); if (p_dummy_hwpic == NULL) return VLC_EGENERIC; assert(p_dummy_hwpic->p_sys); assert(p_dummy_hwpic->p_sys->hw.p_surface); assert(p_dummy_hwpic->p_sys->hw.p_jsurface); p_sys->video.p_surface = p_dummy_hwpic->p_sys->hw.p_surface; p_sys->video.p_jsurface = p_dummy_hwpic->p_sys->hw.p_jsurface; picture_Release(p_dummy_hwpic); return VLC_SUCCESS; } /***************************************************************************** * StartMediaCodec: Create the mediacodec instance *****************************************************************************/ static int StartMediaCodec(decoder_t *p_dec) { decoder_sys_t *p_sys = p_dec->p_sys; union mc_api_args args; if (p_dec->fmt_in.i_cat == VIDEO_ES) { args.video.i_width = p_dec->fmt_out.video.i_width; args.video.i_height = p_dec->fmt_out.video.i_height; args.video.i_angle = p_sys->video.i_angle; args.video.p_surface = p_sys->video.p_surface; args.video.p_jsurface = p_sys->video.p_jsurface; args.video.b_tunneled_playback = args.video.p_surface ? var_InheritBool(p_dec, CFG_PREFIX "tunneled-playback") : false; if (p_sys->b_adaptive) msg_Dbg(p_dec, "mediacodec configured for adaptative playback"); args.video.b_adaptive_playback = p_sys->b_adaptive; } else { date_Set(&p_sys->audio.i_end_date, VLC_TICK_INVALID); args.audio.i_sample_rate = p_dec->fmt_in.audio.i_rate; args.audio.i_channel_count = p_dec->p_sys->audio.i_channels; } return p_sys->api.start(&p_sys->api, &args); } /***************************************************************************** * StopMediaCodec: Close the mediacodec instance *****************************************************************************/ static void StopMediaCodec(decoder_t *p_dec) { decoder_sys_t *p_sys = p_dec->p_sys; /* Remove all pictures that are currently in flight in order * to prevent the vout from using destroyed output buffers. */ if (p_sys->api.b_direct_rendering) RemoveInflightPictures(p_dec); p_sys->api.stop(&p_sys->api); } /***************************************************************************** * OpenDecoder: Create the decoder instance *****************************************************************************/ static int OpenDecoder(vlc_object_t *p_this, pf_MediaCodecApi_init pf_init) { decoder_t *p_dec = (decoder_t *)p_this; decoder_sys_t *p_sys; int i_ret; int i_profile = p_dec->fmt_in.i_profile; const char *mime = NULL; /* Video or Audio if "mediacodec-audio" bool is true */ if (p_dec->fmt_in.i_cat != VIDEO_ES && (p_dec->fmt_in.i_cat != AUDIO_ES || !var_InheritBool(p_dec, CFG_PREFIX "audio"))) return VLC_EGENERIC; /* Fail if this module already failed to decode this ES */ if (var_Type(p_dec, "mediacodec-failed") != 0) return VLC_EGENERIC; if (p_dec->fmt_in.i_cat == VIDEO_ES) { /* Not all mediacodec versions can handle a size of 0. Hopefully, the * packetizer will trigger a decoder restart when a new video size is * found. */ if (!p_dec->fmt_in.video.i_width || !p_dec->fmt_in.video.i_height) return VLC_EGENERIC; switch (p_dec->fmt_in.i_codec) { case VLC_CODEC_HEVC: if (i_profile == -1) { uint8_t i_hevc_profile; if (hevc_get_profile_level(&p_dec->fmt_in, &i_hevc_profile, NULL, NULL)) i_profile = i_hevc_profile; } mime = "video/hevc"; break; case VLC_CODEC_H264: if (i_profile == -1) { uint8_t i_h264_profile; if (h264_get_profile_level(&p_dec->fmt_in, &i_h264_profile, NULL, NULL)) i_profile = i_h264_profile; } mime = "video/avc"; break; case VLC_CODEC_H263: mime = "video/3gpp"; break; case VLC_CODEC_MP4V: mime = "video/mp4v-es"; break; case VLC_CODEC_MPGV: case VLC_CODEC_MP2V: mime = "video/mpeg2"; break; case VLC_CODEC_WMV3: mime = "video/x-ms-wmv"; break; case VLC_CODEC_VC1: mime = "video/wvc1"; break; case VLC_CODEC_VP8: mime = "video/x-vnd.on2.vp8"; break; case VLC_CODEC_VP9: mime = "video/x-vnd.on2.vp9"; break; } } else { switch (p_dec->fmt_in.i_codec) { case VLC_CODEC_AMR_NB: mime = "audio/3gpp"; break; case VLC_CODEC_AMR_WB: mime = "audio/amr-wb"; break; case VLC_CODEC_MPGA: case VLC_CODEC_MP3: mime = "audio/mpeg"; break; case VLC_CODEC_MP2: mime = "audio/mpeg-L2"; break; case VLC_CODEC_MP4A: mime = "audio/mp4a-latm"; break; case VLC_CODEC_QCELP: mime = "audio/qcelp"; break; case VLC_CODEC_VORBIS: mime = "audio/vorbis"; break; case VLC_CODEC_OPUS: mime = "audio/opus"; break; case VLC_CODEC_ALAW: mime = "audio/g711-alaw"; break; case VLC_CODEC_MULAW: mime = "audio/g711-mlaw"; break; case VLC_CODEC_FLAC: mime = "audio/flac"; break; case VLC_CODEC_GSM: mime = "audio/gsm"; break; case VLC_CODEC_A52: mime = "audio/ac3"; break; case VLC_CODEC_EAC3: mime = "audio/eac3"; break; case VLC_CODEC_ALAC: mime = "audio/alac"; break; case VLC_CODEC_DTS: mime = "audio/vnd.dts"; break; /* case VLC_CODEC_: mime = "audio/mpeg-L1"; break; */ /* case VLC_CODEC_: mime = "audio/aac-adts"; break; */ } } if (!mime) { msg_Dbg(p_dec, "codec %4.4s not supported", (char *)&p_dec->fmt_in.i_codec); return VLC_EGENERIC; } /* Allocate the memory needed to store the decoder's structure */ if ((p_sys = calloc(1, sizeof(*p_sys))) == NULL) return VLC_ENOMEM; p_sys->api.p_obj = p_this; p_sys->api.i_codec = p_dec->fmt_in.i_codec; p_sys->api.i_cat = p_dec->fmt_in.i_cat; p_sys->api.psz_mime = mime; p_sys->video.i_mpeg_dar_num = 0; p_sys->video.i_mpeg_dar_den = 0; if (pf_init(&p_sys->api) != 0) { free(p_sys); return VLC_EGENERIC; } if (p_sys->api.configure(&p_sys->api, i_profile) != 0) { /* If the device can't handle video/wvc1, * it can probably handle video/x-ms-wmv */ if (!strcmp(mime, "video/wvc1") && p_dec->fmt_in.i_codec == VLC_CODEC_VC1) { p_sys->api.psz_mime = "video/x-ms-wmv"; if (p_sys->api.configure(&p_sys->api, i_profile) != 0) { p_sys->api.clean(&p_sys->api); free(p_sys); return (VLC_EGENERIC); } } else { p_sys->api.clean(&p_sys->api); free(p_sys); return VLC_EGENERIC; } } p_dec->p_sys = p_sys; vlc_mutex_init(&p_sys->lock); vlc_cond_init(&p_sys->cond); vlc_cond_init(&p_sys->dec_cond); if (p_dec->fmt_in.i_cat == VIDEO_ES) { switch (p_dec->fmt_in.i_codec) { case VLC_CODEC_H264: case VLC_CODEC_HEVC: hxxx_helper_init(&p_sys->video.hh, VLC_OBJECT(p_dec), p_dec->fmt_in.i_codec, false); break; } p_sys->pf_on_new_block = Video_OnNewBlock; p_sys->pf_on_flush = Video_OnFlush; p_sys->pf_process_output = Video_ProcessOutput; p_sys->video.timestamp_fifo = timestamp_FifoNew(32); if (!p_sys->video.timestamp_fifo) goto bailout; TAB_INIT(p_sys->video.i_inflight_pictures, p_sys->video.pp_inflight_pictures); if (var_InheritBool(p_dec, CFG_PREFIX "dr")) { /* Direct rendering: Request a valid OPAQUE Vout in order to get * the surface attached to it */ p_dec->fmt_out.i_codec = VLC_CODEC_ANDROID_OPAQUE; if (p_sys->api.b_support_rotation) { switch (p_dec->fmt_in.video.orientation) { case ORIENT_ROTATED_90: p_sys->video.i_angle = 90; break; case ORIENT_ROTATED_180: p_sys->video.i_angle = 180; break; case ORIENT_ROTATED_270: p_sys->video.i_angle = 270; break; default: p_sys->video.i_angle = 0; break; } } else p_sys->video.i_angle = 0; p_dec->fmt_out.video = p_dec->fmt_in.video; if (p_dec->fmt_out.video.i_sar_num * p_dec->fmt_out.video.i_sar_den == 0) { p_dec->fmt_out.video.i_sar_num = 1; p_dec->fmt_out.video.i_sar_den = 1; } p_sys->video.i_input_width = p_dec->fmt_out.video.i_visible_width = p_dec->fmt_out.video.i_width; p_sys->video.i_input_height = p_dec->fmt_out.video.i_visible_height = p_dec->fmt_out.video.i_height; if (UpdateVout(p_dec) != VLC_SUCCESS) { msg_Err(p_dec, "Opaque Vout request failed"); goto bailout; } } } else { p_sys->pf_on_new_block = Audio_OnNewBlock; p_sys->pf_on_flush = Audio_OnFlush; p_sys->pf_process_output = Audio_ProcessOutput; p_sys->audio.i_channels = p_dec->fmt_in.audio.i_channels; if ((p_sys->api.i_quirks & MC_API_AUDIO_QUIRKS_NEED_CHANNELS) && !p_sys->audio.i_channels) { msg_Warn(p_dec, "codec need a valid channel count"); goto bailout; } p_dec->fmt_out.audio = p_dec->fmt_in.audio; } /* Try first to configure CSD */ if (ParseExtra(p_dec) != VLC_SUCCESS) goto bailout; if ((p_sys->api.i_quirks & MC_API_QUIRKS_NEED_CSD) && !p_sys->i_csd_count && !p_sys->b_adaptive) { switch (p_dec->fmt_in.i_codec) { case VLC_CODEC_H264: case VLC_CODEC_HEVC: break; /* CSDs will come from hxxx_helper */ default: msg_Warn(p_dec, "Not CSD found for %4.4s", (const char *) &p_dec->fmt_in.i_codec); goto bailout; } } i_ret = StartMediaCodec(p_dec); if (i_ret != VLC_SUCCESS) { msg_Err(p_dec, "StartMediaCodec failed"); goto bailout; } if (vlc_clone(&p_sys->out_thread, OutThread, p_dec, VLC_THREAD_PRIORITY_LOW)) { msg_Err(p_dec, "vlc_clone failed"); vlc_mutex_unlock(&p_sys->lock); goto bailout; } p_dec->pf_decode = DecodeBlock; p_dec->pf_flush = DecodeFlush; return VLC_SUCCESS; bailout: CleanDecoder(p_dec); return VLC_EGENERIC; } static int OpenDecoderNdk(vlc_object_t *p_this) { return OpenDecoder(p_this, MediaCodecNdk_Init); } static int OpenDecoderJni(vlc_object_t *p_this) { return OpenDecoder(p_this, MediaCodecJni_Init); } static void AbortDecoderLocked(decoder_t *p_dec) { decoder_sys_t *p_sys = p_dec->p_sys; if (!p_sys->b_aborted) { p_sys->b_aborted = true; vlc_cancel(p_sys->out_thread); } } static void CleanDecoder(decoder_t *p_dec) { decoder_sys_t *p_sys = p_dec->p_sys; vlc_mutex_destroy(&p_sys->lock); vlc_cond_destroy(&p_sys->cond); vlc_cond_destroy(&p_sys->dec_cond); StopMediaCodec(p_dec); CSDFree(p_dec); p_sys->api.clean(&p_sys->api); if (p_dec->fmt_in.i_cat == VIDEO_ES) { if (p_dec->fmt_in.i_codec == VLC_CODEC_H264 || p_dec->fmt_in.i_codec == VLC_CODEC_HEVC) hxxx_helper_clean(&p_sys->video.hh); if (p_sys->video.timestamp_fifo) timestamp_FifoRelease(p_sys->video.timestamp_fifo); } free(p_sys); } /***************************************************************************** * CloseDecoder: Close the decoder instance *****************************************************************************/ static void CloseDecoder(vlc_object_t *p_this) { decoder_t *p_dec = (decoder_t *)p_this; decoder_sys_t *p_sys = p_dec->p_sys; vlc_mutex_lock(&p_sys->lock); /* Unblock output thread waiting in dequeue_out */ DecodeFlushLocked(p_dec); /* Cancel the output thread */ AbortDecoderLocked(p_dec); vlc_mutex_unlock(&p_sys->lock); vlc_join(p_sys->out_thread, NULL); CleanDecoder(p_dec); } /***************************************************************************** * vout callbacks *****************************************************************************/ static void ReleasePicture(decoder_t *p_dec, unsigned i_index, bool b_render) { decoder_sys_t *p_sys = p_dec->p_sys; p_sys->api.release_out(&p_sys->api, i_index, b_render); } static void ReleasePictureTs(decoder_t *p_dec, unsigned i_index, vlc_tick_t i_ts) { decoder_sys_t *p_sys = p_dec->p_sys; assert(p_sys->api.release_out_ts); p_sys->api.release_out_ts(&p_sys->api, i_index, i_ts * INT64_C(1000)); } static void InvalidateAllPictures(decoder_t *p_dec) { decoder_sys_t *p_sys = p_dec->p_sys; for (unsigned int i = 0; i < p_sys->video.i_inflight_pictures; ++i) AndroidOpaquePicture_Release(p_sys->video.pp_inflight_pictures[i], false); } static int InsertInflightPicture(decoder_t *p_dec, picture_sys_t *p_picsys) { decoder_sys_t *p_sys = p_dec->p_sys; if (!p_picsys->hw.p_dec) { p_picsys->hw.p_dec = p_dec; p_picsys->hw.pf_release = ReleasePicture; if (p_sys->api.release_out_ts) p_picsys->hw.pf_release_ts = ReleasePictureTs; TAB_APPEND_CAST((picture_sys_t **), p_sys->video.i_inflight_pictures, p_sys->video.pp_inflight_pictures, p_picsys); } /* else already attached */ return 0; } static void RemoveInflightPictures(decoder_t *p_dec) { decoder_sys_t *p_sys = p_dec->p_sys; for (unsigned int i = 0; i < p_sys->video.i_inflight_pictures; ++i) AndroidOpaquePicture_DetachDecoder(p_sys->video.pp_inflight_pictures[i]); TAB_CLEAN(p_sys->video.i_inflight_pictures, p_sys->video.pp_inflight_pictures); } static int Video_ProcessOutput(decoder_t *p_dec, mc_api_out *p_out, picture_t **pp_out_pic, block_t **pp_out_block) { decoder_sys_t *p_sys = p_dec->p_sys; (void) pp_out_block; assert(pp_out_pic); if (p_out->type == MC_OUT_TYPE_BUF) { picture_t *p_pic = NULL; /* If the oldest input block had no PTS, the timestamp of * the frame returned by MediaCodec might be wrong so we * overwrite it with the corresponding dts. Call FifoGet * first in order to avoid a gap if buffers are released * due to an invalid format or a preroll */ int64_t forced_ts = timestamp_FifoGet(p_sys->video.timestamp_fifo); if (!p_sys->b_has_format) { msg_Warn(p_dec, "Buffers returned before output format is set, dropping frame"); return p_sys->api.release_out(&p_sys->api, p_out->buf.i_index, false); } if (p_out->buf.i_ts <= p_sys->i_preroll_end) return p_sys->api.release_out(&p_sys->api, p_out->buf.i_index, false); if (!p_sys->api.b_direct_rendering && p_out->buf.p_ptr == NULL) { /* This can happen when receiving an EOS buffer */ msg_Warn(p_dec, "Invalid buffer, dropping frame"); return p_sys->api.release_out(&p_sys->api, p_out->buf.i_index, false); } p_pic = decoder_NewPicture(p_dec); if (!p_pic) { msg_Warn(p_dec, "NewPicture failed"); return p_sys->api.release_out(&p_sys->api, p_out->buf.i_index, false); } if (forced_ts == VLC_TICK_INVALID) p_pic->date = p_out->buf.i_ts; else p_pic->date = forced_ts; p_pic->b_progressive = true; if (p_sys->api.b_direct_rendering) { p_pic->p_sys->hw.i_index = p_out->buf.i_index; InsertInflightPicture(p_dec, p_pic->p_sys); } else { unsigned int chroma_div; GetVlcChromaSizes(p_dec->fmt_out.i_codec, p_dec->fmt_out.video.i_width, p_dec->fmt_out.video.i_height, NULL, NULL, &chroma_div); CopyOmxPicture(p_sys->video.i_pixel_format, p_pic, p_sys->video.i_slice_height, p_sys->video.i_stride, (uint8_t *)p_out->buf.p_ptr, chroma_div, NULL); if (p_sys->api.release_out(&p_sys->api, p_out->buf.i_index, false)) { picture_Release(p_pic); return -1; } } assert(!(*pp_out_pic)); *pp_out_pic = p_pic; return 1; } else { assert(p_out->type == MC_OUT_TYPE_CONF); p_sys->video.i_pixel_format = p_out->conf.video.pixel_format; const char *name = "unknown"; if (!p_sys->api.b_direct_rendering && !GetVlcChromaFormat(p_sys->video.i_pixel_format, &p_dec->fmt_out.i_codec, &name)) { msg_Err(p_dec, "color-format not recognized"); return -1; } msg_Err(p_dec, "output: %d %s, %dx%d stride %d %d, crop %d %d %d %d", p_sys->video.i_pixel_format, name, p_out->conf.video.width, p_out->conf.video.height, p_out->conf.video.stride, p_out->conf.video.slice_height, p_out->conf.video.crop_left, p_out->conf.video.crop_top, p_out->conf.video.crop_right, p_out->conf.video.crop_bottom); int i_width = p_out->conf.video.crop_right + 1 - p_out->conf.video.crop_left; int i_height = p_out->conf.video.crop_bottom + 1 - p_out->conf.video.crop_top; if (i_width <= 1 || i_height <= 1) { i_width = p_out->conf.video.width; i_height = p_out->conf.video.height; } if (!(p_sys->api.i_quirks & MC_API_VIDEO_QUIRKS_IGNORE_SIZE)) { p_dec->fmt_out.video.i_visible_width = p_dec->fmt_out.video.i_width = i_width; p_dec->fmt_out.video.i_visible_height = p_dec->fmt_out.video.i_height = i_height; } else { p_dec->fmt_out.video.i_visible_width = p_dec->fmt_out.video.i_width = p_sys->video.i_input_width; p_dec->fmt_out.video.i_visible_height = p_dec->fmt_out.video.i_height = p_sys->video.i_input_height; msg_Dbg(p_dec, "video size ignored from MediaCodec"); } p_sys->video.i_stride = p_out->conf.video.stride; p_sys->video.i_slice_height = p_out->conf.video.slice_height; if (p_sys->video.i_stride <= 0) p_sys->video.i_stride = p_out->conf.video.width; if (p_sys->video.i_slice_height <= 0) p_sys->video.i_slice_height = p_out->conf.video.height; if (p_sys->video.i_pixel_format == OMX_TI_COLOR_FormatYUV420PackedSemiPlanar) p_sys->video.i_slice_height -= p_out->conf.video.crop_top/2; if ((p_sys->api.i_quirks & MC_API_VIDEO_QUIRKS_IGNORE_PADDING)) { p_sys->video.i_slice_height = 0; p_sys->video.i_stride = p_dec->fmt_out.video.i_width; } if (UpdateVout(p_dec) != VLC_SUCCESS) { msg_Err(p_dec, "UpdateVout failed"); return -1; } p_sys->b_has_format = true; return 0; } } /* samples will be in the following order: FL FR FC LFE BL BR BC SL SR */ static uint32_t pi_audio_order_src[] = { AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT, AOUT_CHAN_CENTER, AOUT_CHAN_LFE, AOUT_CHAN_REARLEFT, AOUT_CHAN_REARRIGHT, AOUT_CHAN_REARCENTER, AOUT_CHAN_MIDDLELEFT, AOUT_CHAN_MIDDLERIGHT, }; static int Audio_ProcessOutput(decoder_t *p_dec, mc_api_out *p_out, picture_t **pp_out_pic, block_t **pp_out_block) { decoder_sys_t *p_sys = p_dec->p_sys; (void) pp_out_pic; assert(pp_out_block); if (p_out->type == MC_OUT_TYPE_BUF) { block_t *p_block = NULL; if (p_out->buf.p_ptr == NULL) { /* This can happen when receiving an EOS buffer */ msg_Warn(p_dec, "Invalid buffer, dropping frame"); return p_sys->api.release_out(&p_sys->api, p_out->buf.i_index, false); } if (!p_sys->b_has_format) { msg_Warn(p_dec, "Buffers returned before output format is set, dropping frame"); return p_sys->api.release_out(&p_sys->api, p_out->buf.i_index, false); } p_block = block_Alloc(p_out->buf.i_size); if (!p_block) return -1; p_block->i_nb_samples = p_out->buf.i_size / p_dec->fmt_out.audio.i_bytes_per_frame; if (p_sys->audio.b_extract) { aout_ChannelExtract(p_block->p_buffer, p_dec->fmt_out.audio.i_channels, p_out->buf.p_ptr, p_sys->audio.i_channels, p_block->i_nb_samples, p_sys->audio.pi_extraction, p_dec->fmt_out.audio.i_bitspersample); } else memcpy(p_block->p_buffer, p_out->buf.p_ptr, p_out->buf.i_size); if (p_out->buf.i_ts != 0 && p_out->buf.i_ts != date_Get(&p_sys->audio.i_end_date)) date_Set(&p_sys->audio.i_end_date, p_out->buf.i_ts); p_block->i_pts = date_Get(&p_sys->audio.i_end_date); p_block->i_length = date_Increment(&p_sys->audio.i_end_date, p_block->i_nb_samples) - p_block->i_pts; if (p_sys->api.release_out(&p_sys->api, p_out->buf.i_index, false)) { block_Release(p_block); return -1; } *pp_out_block = p_block; return 1; } else { uint32_t i_layout_dst; int i_channels_dst; assert(p_out->type == MC_OUT_TYPE_CONF); if (p_out->conf.audio.channel_count <= 0 || p_out->conf.audio.channel_count > 8 || p_out->conf.audio.sample_rate <= 0) { msg_Warn(p_dec, "invalid audio properties channels count %d, sample rate %d", p_out->conf.audio.channel_count, p_out->conf.audio.sample_rate); return -1; } msg_Err(p_dec, "output: channel_count: %d, channel_mask: 0x%X, rate: %d", p_out->conf.audio.channel_count, p_out->conf.audio.channel_mask, p_out->conf.audio.sample_rate); p_dec->fmt_out.i_codec = VLC_CODEC_S16N; p_dec->fmt_out.audio.i_format = p_dec->fmt_out.i_codec; p_dec->fmt_out.audio.i_rate = p_out->conf.audio.sample_rate; date_Init(&p_sys->audio.i_end_date, p_out->conf.audio.sample_rate, 1); p_sys->audio.i_channels = p_out->conf.audio.channel_count; p_sys->audio.b_extract = aout_CheckChannelExtraction(p_sys->audio.pi_extraction, &i_layout_dst, &i_channels_dst, NULL, pi_audio_order_src, p_sys->audio.i_channels); if (p_sys->audio.b_extract) msg_Warn(p_dec, "need channel extraction: %d -> %d", p_sys->audio.i_channels, i_channels_dst); p_dec->fmt_out.audio.i_physical_channels = i_layout_dst; aout_FormatPrepare(&p_dec->fmt_out.audio); if (decoder_UpdateAudioFormat(p_dec)) return -1; p_sys->b_has_format = true; return 0; } } static void DecodeFlushLocked(decoder_t *p_dec) { decoder_sys_t *p_sys = p_dec->p_sys; bool b_had_input = p_sys->b_input_dequeued; p_sys->b_input_dequeued = false; p_sys->b_flush_out = true; p_sys->i_preroll_end = 0; p_sys->b_output_ready = false; /* Resend CODEC_CONFIG buffer after a flush */ p_sys->i_csd_send = 0; p_sys->pf_on_flush(p_dec); if (b_had_input && p_sys->api.flush(&p_sys->api) != VLC_SUCCESS) { AbortDecoderLocked(p_dec); return; } vlc_cond_broadcast(&p_sys->cond); while (!p_sys->b_aborted && p_sys->b_flush_out) vlc_cond_wait(&p_sys->dec_cond, &p_sys->lock); } static void DecodeFlush(decoder_t *p_dec) { decoder_sys_t *p_sys = p_dec->p_sys; vlc_mutex_lock(&p_sys->lock); DecodeFlushLocked(p_dec); vlc_mutex_unlock(&p_sys->lock); } static void *OutThread(void *data) { decoder_t *p_dec = data; decoder_sys_t *p_sys = p_dec->p_sys; vlc_mutex_lock(&p_sys->lock); mutex_cleanup_push(&p_sys->lock); for (;;) { int i_index; /* Wait for output ready */ while (!p_sys->b_flush_out && !p_sys->b_output_ready) vlc_cond_wait(&p_sys->cond, &p_sys->lock); if (p_sys->b_flush_out) { /* Acknowledge flushed state */ p_sys->b_flush_out = false; vlc_cond_broadcast(&p_sys->dec_cond); continue; } int canc = vlc_savecancel(); vlc_mutex_unlock(&p_sys->lock); /* Wait for an output buffer. This function returns when a new output * is available or if output is flushed. */ i_index = p_sys->api.dequeue_out(&p_sys->api, -1); vlc_mutex_lock(&p_sys->lock); /* Ignore dequeue_out errors caused by flush */ if (p_sys->b_flush_out) { /* If i_index >= 0, Release it. There is no way to know if i_index * is owned by us, so don't check the error. */ if (i_index >= 0) p_sys->api.release_out(&p_sys->api, i_index, false); /* Parse output format/buffers even when we are flushing */ if (i_index != MC_API_INFO_OUTPUT_FORMAT_CHANGED && i_index != MC_API_INFO_OUTPUT_BUFFERS_CHANGED) { vlc_restorecancel(canc); continue; } } /* Process output returned by dequeue_out */ if (i_index >= 0 || i_index == MC_API_INFO_OUTPUT_FORMAT_CHANGED || i_index == MC_API_INFO_OUTPUT_BUFFERS_CHANGED) { struct mc_api_out out; int i_ret = p_sys->api.get_out(&p_sys->api, i_index, &out); if (i_ret == 1) { picture_t *p_pic = NULL; block_t *p_block = NULL; if (p_sys->pf_process_output(p_dec, &out, &p_pic, &p_block) == -1 && !out.b_eos) { msg_Err(p_dec, "pf_process_output failed"); vlc_restorecancel(canc); break; } if (p_pic) decoder_QueueVideo(p_dec, p_pic); else if (p_block) decoder_QueueAudio(p_dec, p_block); if (out.b_eos) { msg_Warn(p_dec, "EOS received"); p_sys->b_drained = true; vlc_cond_signal(&p_sys->dec_cond); } } else if (i_ret != 0) { msg_Err(p_dec, "get_out failed"); vlc_restorecancel(canc); break; } } else { vlc_restorecancel(canc); break; } vlc_restorecancel(canc); } msg_Warn(p_dec, "OutThread stopped"); /* Signal DecoderFlush that the output thread aborted */ p_sys->b_aborted = true; vlc_cond_signal(&p_sys->dec_cond); vlc_cleanup_pop(); vlc_mutex_unlock(&p_sys->lock); return NULL; } static block_t *GetNextBlock(decoder_sys_t *p_sys, block_t *p_block) { if (p_sys->i_csd_send < p_sys->i_csd_count) return p_sys->pp_csd[p_sys->i_csd_send++]; else return p_block; } static int QueueBlockLocked(decoder_t *p_dec, block_t *p_in_block, bool b_drain) { decoder_sys_t *p_sys = p_dec->p_sys; block_t *p_block = NULL; bool b_dequeue_timeout = false; assert(p_sys->api.b_started); if ((p_sys->api.i_quirks & MC_API_QUIRKS_NEED_CSD) && !p_sys->i_csd_count && !p_sys->b_adaptive) return VLC_EGENERIC; /* Wait for CSDs */ /* Queue CSD blocks and input blocks */ while (b_drain || (p_block = GetNextBlock(p_sys, p_in_block))) { int i_index; vlc_mutex_unlock(&p_sys->lock); /* Wait for an input buffer. This function returns when a new input * buffer is available or after 2secs of timeout. */ i_index = p_sys->api.dequeue_in(&p_sys->api, p_sys->api.b_direct_rendering ? INT64_C(2000000) : -1); vlc_mutex_lock(&p_sys->lock); if (p_sys->b_aborted) return VLC_EGENERIC; bool b_config = false; vlc_tick_t i_ts = 0; p_sys->b_input_dequeued = true; const void *p_buf = NULL; size_t i_size = 0; if (i_index >= 0) { assert(b_drain || p_block != NULL); if (p_block != NULL) { b_config = (p_block->i_flags & BLOCK_FLAG_CSD); if (!b_config) { i_ts = p_block->i_pts; if (!i_ts && p_block->i_dts) i_ts = p_block->i_dts; } p_buf = p_block->p_buffer; i_size = p_block->i_buffer; } if (p_sys->api.queue_in(&p_sys->api, i_index, p_buf, i_size, i_ts, b_config) == 0) { if (!b_config && p_block != NULL) { if (p_block->i_flags & BLOCK_FLAG_PREROLL) p_sys->i_preroll_end = i_ts; /* One input buffer is queued, signal OutThread that will * fetch output buffers */ p_sys->b_output_ready = true; vlc_cond_broadcast(&p_sys->cond); assert(p_block == p_in_block), p_in_block = NULL; } b_dequeue_timeout = false; if (b_drain) break; } else { msg_Err(p_dec, "queue_in failed"); goto error; } } else if (i_index == MC_API_INFO_TRYAGAIN) { /* HACK: When direct rendering is enabled, there is a possible * deadlock between the Decoder and the Vout. It happens when the * Vout is paused and when the Decoder is flushing. In that case, * the Vout won't release any output buffers, therefore MediaCodec * won't dequeue any input buffers. To work around this issue, * release all output buffers if DecodeBlock is waiting more than * 2secs for a new input buffer. */ if (!b_dequeue_timeout) { msg_Warn(p_dec, "Decoder stuck: invalidate all buffers"); InvalidateAllPictures(p_dec); b_dequeue_timeout = true; continue; } else { msg_Err(p_dec, "dequeue_in timeout: no input available for 2secs"); goto error; } } else { msg_Err(p_dec, "dequeue_in failed"); goto error; } } if (b_drain) { msg_Warn(p_dec, "EOS sent, waiting for OutThread"); /* Wait for the OutThread to stop (and process all remaining output * frames. Use a timeout here since we can't know if all decoders will * behave correctly. */ vlc_tick_t deadline = mdate() + INT64_C(3000000); while (!p_sys->b_aborted && !p_sys->b_drained && vlc_cond_timedwait(&p_sys->dec_cond, &p_sys->lock, deadline) == 0); if (!p_sys->b_drained) { msg_Err(p_dec, "OutThread timed out"); AbortDecoderLocked(p_dec); } p_sys->b_drained = false; } return VLC_SUCCESS; error: AbortDecoderLocked(p_dec); return VLC_EGENERIC; } static int DecodeBlock(decoder_t *p_dec, block_t *p_in_block) { decoder_sys_t *p_sys = p_dec->p_sys; int i_ret; vlc_mutex_lock(&p_sys->lock); if (p_sys->b_aborted) { if (p_sys->b_has_format) goto end; else goto reload; } if (p_in_block == NULL) { /* No input block, decoder is draining */ msg_Err(p_dec, "Decoder is draining"); if (p_sys->b_output_ready) QueueBlockLocked(p_dec, NULL, true); goto end; } if (p_in_block->i_flags & (BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED)) { if (p_sys->b_output_ready) QueueBlockLocked(p_dec, NULL, true); DecodeFlushLocked(p_dec); if (p_sys->b_aborted) goto end; if (p_in_block->i_flags & BLOCK_FLAG_CORRUPTED) goto end; } if (p_in_block->i_flags & BLOCK_FLAG_INTERLACED_MASK && !(p_sys->api.i_quirks & MC_API_VIDEO_QUIRKS_SUPPORT_INTERLACED)) { /* Before Android 21 and depending on the vendor, MediaCodec can * crash or be in an inconsistent state when decoding interlaced * videos. See OMXCodec_GetQuirks() for a white list of decoders * that supported interlaced videos before Android 21. */ msg_Warn(p_dec, "codec doesn't support interlaced videos"); goto reload; } /* Parse input block */ if ((i_ret = p_sys->pf_on_new_block(p_dec, &p_in_block)) != 1) { if (i_ret != 0) { AbortDecoderLocked(p_dec); msg_Err(p_dec, "pf_on_new_block failed"); } goto end; } if (p_sys->i_decode_flags & (DECODE_FLAG_DRAIN|DECODE_FLAG_RESTART)) { msg_Warn(p_dec, "Draining from DecodeBlock"); const bool b_restart = p_sys->i_decode_flags & DECODE_FLAG_RESTART; p_sys->i_decode_flags = 0; /* Drain and flush before restart to unblock OutThread */ if (p_sys->b_output_ready) QueueBlockLocked(p_dec, NULL, true); DecodeFlushLocked(p_dec); if (p_sys->b_aborted) goto end; if (b_restart) { StopMediaCodec(p_dec); int i_ret = StartMediaCodec(p_dec); switch (i_ret) { case VLC_SUCCESS: msg_Warn(p_dec, "Restarted from DecodeBlock"); break; case VLC_ENOOBJ: break; default: msg_Err(p_dec, "StartMediaCodec failed"); AbortDecoderLocked(p_dec); goto end; } } } /* Abort if MediaCodec is not yet started */ if (p_sys->api.b_started) QueueBlockLocked(p_dec, p_in_block, false); end: if (p_in_block) block_Release(p_in_block); /* Too late to reload here, we already modified/released the input block, * do it next time. */ int ret = p_sys->b_aborted && p_sys->b_has_format ? VLCDEC_ECRITICAL : VLCDEC_SUCCESS; vlc_mutex_unlock(&p_sys->lock); return ret; reload: vlc_mutex_unlock(&p_sys->lock); /* Add an empty variable so that mediacodec won't be loaded again * for this ES */ var_Create(p_dec, "mediacodec-failed", VLC_VAR_VOID); return VLCDEC_RELOAD; } static int Video_OnNewBlock(decoder_t *p_dec, block_t **pp_block) { decoder_sys_t *p_sys = p_dec->p_sys; block_t *p_block = *pp_block; timestamp_FifoPut(p_sys->video.timestamp_fifo, p_block->i_pts ? VLC_TICK_INVALID : p_block->i_dts); return 1; } static int VideoHXXX_OnNewBlock(decoder_t *p_dec, block_t **pp_block) { decoder_sys_t *p_sys = p_dec->p_sys; struct hxxx_helper *hh = &p_sys->video.hh; bool b_config_changed = false; bool *p_config_changed = p_sys->b_adaptive ? NULL : &b_config_changed; *pp_block = hh->pf_process_block(hh, *pp_block, p_config_changed); if (!*pp_block) return 0; if (b_config_changed) { bool b_size_changed; int i_ret; switch (p_dec->fmt_in.i_codec) { case VLC_CODEC_H264: if (hh->h264.i_sps_count > 0 || hh->h264.i_pps_count > 0) i_ret = H264SetCSD(p_dec, &b_size_changed); else i_ret = VLC_EGENERIC; break; case VLC_CODEC_HEVC: if (hh->hevc.i_vps_count > 0 || hh->hevc.i_sps_count > 0 || hh->hevc.i_pps_count > 0 ) i_ret = HEVCSetCSD(p_dec, &b_size_changed); else i_ret = VLC_EGENERIC; break; } if (i_ret != VLC_SUCCESS) return i_ret; if (b_size_changed || !p_sys->api.b_started) { if (p_sys->api.b_started) msg_Err(p_dec, "SPS/PPS changed during playback and " "video size are different. Restart it !"); p_sys->i_decode_flags |= DECODE_FLAG_RESTART; } else { msg_Err(p_dec, "SPS/PPS changed during playback. Drain it"); p_sys->i_decode_flags |= DECODE_FLAG_DRAIN; } } return Video_OnNewBlock(p_dec, pp_block); } static int VideoMPEG2_OnNewBlock(decoder_t *p_dec, block_t **pp_block) { if (pp_block == NULL || (*pp_block)->i_buffer <= 7) return 1; decoder_sys_t *p_sys = p_dec->p_sys; const int startcode = (*pp_block)->p_buffer[3]; /* DAR aspect ratio from the DVD MPEG2 standard */ static const int mpeg2_aspect[16][2] = { {0,0}, /* reserved */ {0,0}, /* DAR = 0:0 will result in SAR = 1:1 */ {4,3}, {16,9}, {221,100}, /* reserved */ {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0} }; if (startcode == 0xB3 /* SEQUENCE_HEADER_STARTCODE */) { int mpeg_dar_code = (*pp_block)->p_buffer[7] >> 4; if (mpeg_dar_code >= 16) return 0; p_sys->video.i_mpeg_dar_num = mpeg2_aspect[mpeg_dar_code][0]; p_sys->video.i_mpeg_dar_den = mpeg2_aspect[mpeg_dar_code][1]; } return 1; } static int VideoVC1_OnNewBlock(decoder_t *p_dec, block_t **pp_block) { block_t *p_block = *pp_block; /* Adding frame start code */ p_block = *pp_block = block_Realloc(p_block, 4, p_block->i_buffer); if (p_block == NULL) return VLC_ENOMEM; p_block->p_buffer[0] = 0x00; p_block->p_buffer[1] = 0x00; p_block->p_buffer[2] = 0x01; p_block->p_buffer[3] = 0x0d; return Video_OnNewBlock(p_dec, pp_block); } static void Video_OnFlush(decoder_t *p_dec) { decoder_sys_t *p_sys = p_dec->p_sys; timestamp_FifoEmpty(p_sys->video.timestamp_fifo); /* Invalidate all pictures that are currently in flight * since flushing make all previous indices returned by * MediaCodec invalid. */ if (p_sys->api.b_direct_rendering) InvalidateAllPictures(p_dec); } static int Audio_OnNewBlock(decoder_t *p_dec, block_t **pp_block) { decoder_sys_t *p_sys = p_dec->p_sys; block_t *p_block = *pp_block; /* We've just started the stream, wait for the first PTS. */ if (!date_Get(&p_sys->audio.i_end_date)) { if (p_block->i_pts <= VLC_TICK_INVALID) return 0; date_Set(&p_sys->audio.i_end_date, p_block->i_pts); } return 1; } static void Audio_OnFlush(decoder_t *p_dec) { decoder_sys_t *p_sys = p_dec->p_sys; date_Set(&p_sys->audio.i_end_date, VLC_TICK_INVALID); }