From a69e27f67fa4f77d58fd17f03ce158b076a537f1 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Kempf Date: Sun, 19 Dec 2010 00:42:10 +0100 Subject: [PATCH] Broadcom CrystalHD decoder support This new module works with BCM70015 Crystal HD video decoders It handles hardware decoding of H264, VC-1 and mpeg2 So far, it only works on GNU/Linux with the appropriate driver and library. Windows version is still limited. This is the first part, since mpeg4, interlaced and BCM70012 are not working yet. --- NEWS | 1 + configure.ac | 19 ++ modules/codec/Modules.am | 1 + modules/codec/crystalhd.c | 531 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 552 insertions(+) create mode 100644 modules/codec/crystalhd.c diff --git a/NEWS b/NEWS index 8b888d2da2..f340074368 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,7 @@ Access Codecs * You can now use ffmpeg-mt in conjunction with vlc * Important fixes for RealVideo 3.0 and 4.0 playback, notably in MKV + * Experimental Hardware decoding using Broadcom CrystalHD cards Core: * Playback rate doesn't get resetted to 1 between items anymore diff --git a/configure.ac b/configure.ac index c589a4a47f..eb5e29b51e 100644 --- a/configure.ac +++ b/configure.ac @@ -2451,6 +2451,25 @@ then VLC_ADD_LIBS([omxil], [$LIBDL]) fi +dnl +dnl CrystalHD codec plugin +dnl +AC_ARG_ENABLE(crystalhd, + [ --enable-crystalhd crystalhd codec plugin (default disabled)]) +if test "${enable_crystalhd}" == "yes"; then + AC_CHECK_HEADERS(libcrystalhd/libcrystalhd_if.h, [ + VLC_ADD_PLUGIN([crystalhd]) + VLC_ADD_LIBS([crystalhd], [-lcrystalhd]) + ],[ + AC_CHECK_HEADERS(libcrystalhd/bc_drv_if.h, [ + VLC_ADD_PLUGIN([crystalhd]) + VLC_ADD_LIBS([crystalhd], [-lbcmDIL]) + ],[ + AC_MSG_ERROR("Could not find CrystalHD development headers") + ]) + ]) +fi + dnl dnl mad plugin dnl diff --git a/modules/codec/Modules.am b/modules/codec/Modules.am index 8867b8cebc..3ac38b9e0e 100644 --- a/modules/codec/Modules.am +++ b/modules/codec/Modules.am @@ -37,6 +37,7 @@ SOURCES_aes3 = aes3.c SOURCES_subsdec = subsass.c subsdec.c subsdec.h SOURCES_subsusf = subsusf.c subsdec.h SOURCES_t140 = t140.c +SOURCES_crystalhd = crystalhd.c libvlc_LTLIBRARIES += \ liba52_plugin.la \ diff --git a/modules/codec/crystalhd.c b/modules/codec/crystalhd.c new file mode 100644 index 0000000000..632c044db9 --- /dev/null +++ b/modules/codec/crystalhd.c @@ -0,0 +1,531 @@ +/***************************************************************************** + * Copyright © 2010 VideoLAN + * + * Authors: Jean-Baptiste Kempf + * Narendra Sankar + * + * 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 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 + +/* VLC includes */ +#include +#include +#include + +#if !defined(_WIN32) && !defined(__APPLE__) + #define __LINUX_USER__ +#endif + +/* CrystalHD */ +#include +#include +#if HAVE_LIBCRYSTALHD_BC_DRV_IF_H + #include +#else + #include +#endif + +#include + +/* BC pts are multiple of 100ns */ +#define TO_BC_PTS( a ) ( a * 10 + 1 ) +#define FROM_BC_PTS( a ) ((a - 1) /10) + +//#define DEBUG_CRYSTALHD 1 + +/***************************************************************************** + * Module descriptor + *****************************************************************************/ +static int OpenDecoder ( vlc_object_t * ); +static void CloseDecoder ( vlc_object_t * ); + +vlc_module_begin () + set_category( CAT_INPUT ) + set_subcategory( SUBCAT_INPUT_VCODEC ) + set_description( N_("Crystal HD hardware video decoder") ) + set_capability( "decoder", 0 ) + set_callbacks( OpenDecoder, CloseDecoder ) + add_shortcut( "crystalhd" ) +vlc_module_end () + +/***************************************************************************** + * Local prototypes + *****************************************************************************/ +static picture_t *DecodeBlock ( decoder_t *p_dec, block_t **pp_block ); +static void crystal_CopyPicture ( picture_t *, BC_DTS_PROC_OUT* ); +static int crystal_insert_sps_pps(decoder_t *, uint8_t *, uint32_t); + +/***************************************************************************** + * decoder_sys_t : CrysalHD decoder structure + *****************************************************************************/ +struct decoder_sys_t +{ + HANDLE bcm_handle; /* Device Handle */ + + uint8_t *p_sps_pps_buf; /* SPS/PPS buffer */ + uint32_t i_sps_pps_size; /* SPS/PPS size */ + + uint32_t i_nal_size; /* NAL header size */ +}; + +/***************************************************************************** + * OpenDecoder: probe the decoder and return score + *****************************************************************************/ +static int OpenDecoder( vlc_object_t *p_this ) +{ + decoder_t *p_dec = (decoder_t*)p_this; + decoder_sys_t *p_sys; + + /* Codec specifics */ + uint32_t i_bcm_codec_subtype = 0; + switch ( p_dec->fmt_in.i_codec ) + { + case VLC_CODEC_H264: + if( p_dec->fmt_in.i_original_fourcc == VLC_FOURCC( 'a', 'v', 'c', '1' ) ) + i_bcm_codec_subtype = BC_MSUBTYPE_AVC1; + else + i_bcm_codec_subtype = BC_MSUBTYPE_H264; + break; + case VLC_CODEC_VC1: + i_bcm_codec_subtype = BC_MSUBTYPE_VC1; + break; + case VLC_CODEC_WMV3: + i_bcm_codec_subtype = BC_MSUBTYPE_WMV3; + break; + case VLC_CODEC_WMVA: + i_bcm_codec_subtype = BC_MSUBTYPE_WMVA; + break; + case VLC_CODEC_MPGV: + i_bcm_codec_subtype = BC_MSUBTYPE_MPEG2VIDEO; + break; +/* Not ready for production yet + case VLC_CODEC_MP4V: + i_bcm_codec_subtype = BC_MSUBTYPE_DIVX; + break; */ + default: + return VLC_EGENERIC; + } + +#ifdef _WIN32 + HINSTANCE p_bcm_dll = LoadLibrary( "bcmDIL.dll" ); + if( !p_bcm_dll ) + { + #ifdef DEBUG_CRYSTALHD + msg_Dbg( p_dec, "Couldn't load the CrystalHD dll"); + #endif + return VLC_EGENERIC + } +#endif + + /* Allocate the memory needed to store the decoder's structure */ + p_sys = malloc( sizeof(*p_sys) ); + if( !p_sys ) + return VLC_ENOMEM; + + /* Fill decoder_sys_t */ + p_dec->p_sys = p_sys; + p_sys->i_nal_size = 4; // assume 4 byte start codes + p_sys->i_sps_pps_size = 0; + p_sys->p_sps_pps_buf = NULL; + + +#ifdef DEBUG_CRYSTALHD + msg_Dbg( p_dec, "Trying to open CrystalHD HW"); +#endif + + /* Get the handle for the device */ + if( DtsDeviceOpen( &p_sys->bcm_handle, + (DTS_PLAYBACK_MODE | DTS_LOAD_FILE_PLAY_FW | DTS_SKIP_TX_CHK_CPB ) ) + // | DTS_DFLT_RESOLUTION(vdecRESOLUTION_720p29_97) ) ) + != BC_STS_SUCCESS ) + { + msg_Err( p_dec, "Couldn't find and open the BCM CrystalHD device" ); + free( p_sys ); + return VLC_EGENERIC; + } + +#ifdef DEBUG_CRYSTALHD + BC_INFO_CRYSTAL info; + if( DtsCrystalHDVersion( p_sys->bcm_handle, &info ) == BC_STS_SUCCESS ) + { + msg_Dbg( p_dec, "Using CrystalHD Driver version: %i.%i.%i, " + "Library version: %i.%i.%i, " + "Firmware version: %i.%i.%i", + info.drvVersion.drvRelease, info.drvVersion.drvMajor, info.drvVersion.drvMinor, + info.dilVersion.dilRelease, info.dilVersion.dilMajor, info.dilVersion.dilMinor, + info.fwVersion.fwRelease, info.fwVersion.fwMajor, info.fwVersion.fwMinor ); + } +#endif + + /* Special case for AVC1 */ + if( i_bcm_codec_subtype == BC_MSUBTYPE_AVC1 ) + { + if( p_dec->fmt_in.i_extra > 0 ) + { + msg_Dbg( p_dec, "Parsing extra infos for avc1" ); + if( crystal_insert_sps_pps( p_dec, (uint8_t*)p_dec->fmt_in.p_extra, + p_dec->fmt_in.i_extra ) != VLC_SUCCESS ) + goto error; + } + else + { + msg_Err( p_dec, "Missing extra infos for avc1" ); + goto error; + } + } + + /* Always YUY2 color */ + if( DtsSetColorSpace( p_sys->bcm_handle, OUTPUT_MODE422_YUY2 ) != BC_STS_SUCCESS ) + { + msg_Err( p_dec, "Couldn't set the color space. Please report this!" ); + goto error; + } + + /* Prepare Input for the device */ + BC_INPUT_FORMAT p_in; + memset( &p_in, 0, sizeof(BC_INPUT_FORMAT) ); + p_in.OptFlags = 0x51; /* 0b 0 1 01 0001 */ + p_in.mSubtype = i_bcm_codec_subtype; + p_in.startCodeSz = p_sys->i_nal_size; + p_in.pMetaData = p_sys->p_sps_pps_buf; + p_in.metaDataSz = p_sys->i_sps_pps_size; + p_in.width = p_dec->fmt_in.video.i_width; + p_in.height = p_dec->fmt_in.video.i_height; + p_in.Progressive = true; + + if( DtsSetInputFormat( p_sys->bcm_handle, &p_in ) != BC_STS_SUCCESS ) + { + msg_Err( p_dec, "Couldn't set the color space. Please report this!" ); + goto error; + } + + /* Open a decoder */ + if( DtsOpenDecoder( p_sys->bcm_handle, BC_STREAM_TYPE_ES ) != BC_STS_SUCCESS ) + { + msg_Err( p_dec, "Couldn't open the CrystalHD decoder" ); + goto error; + } + + /* Start it */ + if( DtsStartDecoder( p_sys->bcm_handle ) != BC_STS_SUCCESS ) + { + msg_Err( p_dec, "Couldn't start the decoder" ); + goto error; + } + + if( DtsStartCapture( p_sys->bcm_handle ) != BC_STS_SUCCESS ) + { + msg_Err( p_dec, "Couldn't start the capture" ); + goto error_complete; + } + + /* Set output properties */ + p_dec->fmt_out.i_cat = VIDEO_ES; + p_dec->fmt_out.i_codec = VLC_CODEC_YUYV; + p_dec->fmt_out.video.i_width = p_dec->fmt_in.video.i_width; + p_dec->fmt_out.video.i_height = p_dec->fmt_in.video.i_height; + p_dec->b_need_packetized = true; + + /* Set callbacks */ + p_dec->pf_decode_video = DecodeBlock; + + msg_Info( p_dec, "Opened CrystalHD hardware with success" ); + return VLC_SUCCESS; + +error_complete: + DtsCloseDecoder( p_sys->bcm_handle ); +error: + DtsDeviceClose( p_sys->bcm_handle ); + free( p_sys ); + return VLC_EGENERIC; +} + +/***************************************************************************** + * CloseDecoder: decoder destruction + *****************************************************************************/ +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; + + if( DtsFlushInput( p_sys->bcm_handle, 2 ) != BC_STS_SUCCESS ) + goto error; + if( DtsStopDecoder( p_sys->bcm_handle ) != BC_STS_SUCCESS ) + goto error; + if( DtsCloseDecoder( p_sys->bcm_handle ) != BC_STS_SUCCESS ) + goto error; + if( DtsDeviceClose( p_sys->bcm_handle ) != BC_STS_SUCCESS ) + goto error; + +error: + free( p_sys->p_sps_pps_buf ); +#ifdef DEBUG_CRYSTALHD + msg_Dbg( p_dec, "done cleaning up CrystalHD" ); +#endif + free( p_sys ); +} + +/**************************************************************************** + * DecodeBlock: the whole thing + ****************************************************************************/ +static picture_t *DecodeBlock( decoder_t *p_dec, block_t **pp_block ) +{ + decoder_sys_t *p_sys = p_dec->p_sys; + block_t *p_block; + + BC_DTS_PROC_OUT proc_out; + BC_DTS_STATUS driver_stat; + + picture_t *p_pic; + + /* First check the status of the decode to produce pictures */ + if( DtsGetDriverStatus( p_sys->bcm_handle, &driver_stat ) != BC_STS_SUCCESS ) + return NULL; + + p_block = *pp_block; + if( p_block ) + { + if( ( p_block->i_flags&(BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED) ) == 0 ) + { + /* Valid input block, so we can send to HW to decode */ + + BC_STATUS status = DtsProcInput( p_sys->bcm_handle, + p_block->p_buffer, + p_block->i_buffer, + p_block->i_pts >= VLC_TS_INVALID ? TO_BC_PTS(p_block->i_pts) : 0, false ); + + block_Release( p_block ); + *pp_block = NULL; + + if( status != BC_STS_SUCCESS ) + return NULL; + } + } +#ifdef DEBUG_CRYSTALHD + else + { + if( driver_stat.ReadyListCount != 0 ) + msg_Err( p_dec, " Input NULL but have pictures %u", driver_stat.ReadyListCount ); + } +#endif + + if( driver_stat.ReadyListCount == 0 ) + return NULL; + + /* Prepare the Output structure */ + /* We always expect and use YUY2 */ + memset( &proc_out, 0, sizeof(BC_DTS_PROC_OUT) ); + proc_out.PicInfo.width = p_dec->fmt_out.video.i_width; + proc_out.PicInfo.height = p_dec->fmt_out.video.i_height; + proc_out.YbuffSz = p_dec->fmt_out.video.i_width * p_dec->fmt_out.video.i_height / 2; + proc_out.Ybuff = malloc( proc_out.YbuffSz * 4); // Allocate in bytes + proc_out.PoutFlags = BC_POUT_FLAGS_SIZE; //FIXME why? + +#ifdef DEBUG_CRYSTALHD + msg_Dbg( p_dec, "%i, %i", p_dec->fmt_out.video.i_width, p_dec->fmt_out.video.i_height ); +#endif + if( !proc_out.Ybuff ) + return NULL; + + BC_STATUS sts = DtsProcOutput( p_sys->bcm_handle, 128, &proc_out ); +#ifdef DEBUG_CRYSTALHD + if( sts != BC_STS_SUCCESS ) + msg_Err( p_dec, "DtsProcOutput returned %i", sts ); +#endif + + uint8_t b_eos; + switch( sts ) + { + case BC_STS_SUCCESS: + if( !(proc_out.PoutFlags & BC_POUT_FLAGS_PIB_VALID) ) + { + msg_Dbg( p_dec, "Invalid PIB" ); + break; + } + + p_pic = decoder_NewPicture( p_dec ); + if( !p_pic ) + break; + + crystal_CopyPicture( p_pic, &proc_out ); + p_pic->date = proc_out.PicInfo.timeStamp > 0 ? FROM_BC_PTS(proc_out.PicInfo.timeStamp) : VLC_TS_INVALID; + //p_pic->date += 100 * 1000; +#ifdef DEBUG_CRYSTALHD + msg_Dbg( p_dec, "TS Output is %"PRIu64, p_pic->date); +#endif + free( proc_out.Ybuff ); + return p_pic; + + case BC_STS_DEC_NOT_OPEN: + case BC_STS_DEC_NOT_STARTED: + msg_Err( p_dec, "Decoder not opened or started" ); + break; + + case BC_STS_INV_ARG: + msg_Warn( p_dec, "Invalid arguments. Please report" ); + break; + + case BC_STS_FMT_CHANGE: /* Format change */ + /* if( !(proc_out.PoutFlags & BC_POUT_FLAGS_PIB_VALID) ) + break; */ + p_dec->fmt_out.video.i_width = proc_out.PicInfo.width; + p_dec->fmt_out.video.i_height = proc_out.PicInfo.height; +#define setAR( a, b, c ) case a: p_dec->fmt_out.video.i_sar_num = b; p_dec->fmt_out.video.i_sar_den = c; break; + switch( proc_out.PicInfo.aspect_ratio ) + { + setAR( vdecAspectRatioSquare, 1, 1 ) + setAR( vdecAspectRatio12_11, 12, 11 ) + setAR( vdecAspectRatio10_11, 10, 11 ) + setAR( vdecAspectRatio16_11, 16, 11 ) + setAR( vdecAspectRatio40_33, 40, 33 ) + setAR( vdecAspectRatio24_11, 24, 11 ) + setAR( vdecAspectRatio20_11, 20, 11 ) + setAR( vdecAspectRatio32_11, 32, 11 ) + setAR( vdecAspectRatio80_33, 80, 33 ) + setAR( vdecAspectRatio18_11, 18, 11 ) + setAR( vdecAspectRatio15_11, 15, 11 ) + setAR( vdecAspectRatio64_33, 64, 33 ) + setAR( vdecAspectRatio160_99, 160, 99 ) + setAR( vdecAspectRatio4_3, 4, 3 ) + setAR( vdecAspectRatio16_9, 16, 9 ) + setAR( vdecAspectRatio221_1, 221, 1 ) + default: break; + } +#undef setAR + msg_Dbg( p_dec, "Format Change Detected [%i, %i], AR: %i/%i", + proc_out.PicInfo.width, proc_out.PicInfo.height, + p_dec->fmt_out.video.i_sar_num, p_dec->fmt_out.video.i_sar_den ); + break; + + /* Nothing is documented here... */ + case BC_STS_NO_DATA: + if( DtsIsEndOfStream( p_sys->bcm_handle, &b_eos ) == BC_STS_SUCCESS ) + if( b_eos ) + msg_Dbg( p_dec, "End of Stream" ); + break; + case BC_STS_TIMEOUT: /* Timeout */ + msg_Err( p_dec, "ProcOutput timeout" ); + break; + case BC_STS_IO_XFR_ERROR: + case BC_STS_IO_USER_ABORT: + case BC_STS_IO_ERROR: + msg_Err( p_dec, "ProcOutput return mode not implemented. Please report" ); + break; + default: + msg_Err( p_dec, "Unknown return status. Please report %i", sts ); + break; + } + free( proc_out.Ybuff ); + return NULL; +} + +/* Copy the data + * FIXME: this should not exist */ +static void crystal_CopyPicture ( picture_t *p_pic, BC_DTS_PROC_OUT* p_out ) +{ + int i_dst_stride; + uint8_t *p_dst, *p_dst_end; + uint8_t *p_src = p_out->Ybuff; + + p_dst = p_pic->p[0].p_pixels; + i_dst_stride = p_pic->p[0].i_pitch; + p_dst_end = p_dst + (i_dst_stride * p_out->PicInfo.height); + + for( ; p_dst < p_dst_end; p_dst += i_dst_stride, p_src += (p_out->PicInfo.width * 2)) + vlc_memcpy( p_dst, p_src, p_out->PicInfo.width * 2); // Copy in bytes +} + +/* Parse the SPS/PPS Metadata to feed the decoder for avc1 */ +static int crystal_insert_sps_pps(decoder_t *p_dec, uint8_t *p_buf, uint32_t i_buf_size) +{ + decoder_sys_t *p_sys = p_dec->p_sys; + int i_profile; + uint32_t i_data_size = i_buf_size, i_nal_size; + unsigned int i_loop_end; + + p_sys->i_sps_pps_size = 0; + + p_sys->p_sps_pps_buf = malloc( p_dec->fmt_in.i_extra * 2 ); + if( !p_sys->p_sps_pps_buf ) + return VLC_ENOMEM; + + /* */ + if( i_data_size < 7 ) + { + msg_Err( p_dec, "Input Metadata too small" ); + goto error; + } + + /* Read infos in first 6 bytes */ + i_profile = (p_buf[1] << 16) | (p_buf[2] << 8) | p_buf[3]; + p_sys->i_nal_size = (p_buf[4] & 0x03) + 1; + p_buf += 5; + i_data_size -= 5; + + for ( unsigned int j = 0; j < 2; j++ ) + { + /* First time is SPS, Second is PPS */ + if (i_data_size < 1) { + msg_Err( p_dec, "PPS too small after processing SPS/PPS %u", i_data_size ); + goto error; + } + i_loop_end = p_buf[0] & (j == 0 ? 0x1f : 0xff); + p_buf++; i_data_size--; + + for ( unsigned int i = 0; i < i_loop_end; i++) + { + if (i_data_size < 2 ) { + msg_Err( p_dec, "SPS is too small %u", i_data_size ); + goto error; + } + + i_nal_size = (p_buf[0] << 8) | p_buf[1]; + p_buf += 2; + i_data_size -= 2; + + if (i_data_size < i_nal_size ) { + msg_Err( p_dec, "SPS size does not match NAL specified size %u", i_data_size ); + goto error; + } + + p_sys->p_sps_pps_buf[p_sys->i_sps_pps_size++] = 0; + p_sys->p_sps_pps_buf[p_sys->i_sps_pps_size++] = 0; + p_sys->p_sps_pps_buf[p_sys->i_sps_pps_size++] = 0; + p_sys->p_sps_pps_buf[p_sys->i_sps_pps_size++] = 1; + + memcpy(p_sys->p_sps_pps_buf + p_sys->i_sps_pps_size, p_buf, i_nal_size); + p_sys->i_sps_pps_size += i_nal_size; + + p_buf += i_nal_size; + i_data_size -= i_nal_size; + } + } + + return VLC_SUCCESS; + +error: + free( p_sys->p_sps_pps_buf ); + p_sys->p_sps_pps_buf = NULL; + return VLC_ENOMEM; +} +