[vlc-devel] CrystalHD decoder

Laurent Aimar fenrir at elivagar.org
Tue Dec 7 20:38:33 CET 2010


Hi,

On Fri, Dec 03, 2010 at 11:05:13AM +0100, Jean-Baptiste Kempf wrote:
> This is a first shot at it. Tested only on 0015 (someone has a 0012
> around?)
> diff --git a/modules/codec/crystalhd.c b/modules/codec/crystalhd.c
> new file mode 100644
> index 0000000..455ba5a
> +/*****************************************************************************
> + * 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;
> +
> +    /* limits codecs */
> +    if( p_dec->fmt_in.i_codec != VLC_CODEC_H264 &&
> +        p_dec->fmt_in.i_codec != VLC_CODEC_VC1  &&
> +        p_dec->fmt_in.i_codec != VLC_CODEC_WMV3 &&
> +        p_dec->fmt_in.i_codec != VLC_CODEC_WMVA &&
> +        p_dec->fmt_in.i_codec != VLC_CODEC_MPGV )
> +    {
> +        return VLC_EGENERIC;
> +    }
> +
> +    /* 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
> +
> +    /* 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;
> +            if( p_dec->fmt_in.i_extra > 0 )
> +            {
> +                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;
> +            }
> +        }
> +        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:
> +        assert(0);
> +    }
 It might be better to move that part to the start to avoid maintaining two lists
of codecs (of course the check on avc1 still need to stay here).

> +#ifdef DEBUG_CRYSTALHD
> +    msg_Dbg(p_dec, "Codec type is %i", i_bcm_codec_subtype);
 As you use uint32_t you should not be using %i.
> +#endif
> +
> +    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;
 I probably already asked you but doesn't CrystalHD support planar YUV?

> +    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;
> +}

> +/****************************************************************************
> + * 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;
 I wonder if you should ignore this error and simply continue to ensure that you
correctly retreives all pending pictures. But it's only meaningful only if the
decoder continues decoding without needed any new buffers.

> +        }
> +    }
> +#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 )
> +            {
> +                free( proc_out.Ybuff );
> +                return NULL;
> +            }
 break seems enought here.
> +
> +            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 );
 I think that in that case you should loop back to DtsProcOutput directly. With
the current code I think it will delay the next pictures until you receive the
next stream buffer. But I don't think it is really serious.

> +            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
 Are PicInfo.width and PicInfo.height the real size or they can contain
padding? If they can, you will have overwrites.
> +}

> +/* 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;
> +    i_loop_end        = p_buf[5] & 0x1f;
> +    p_buf += 6;
> +    i_data_size -= 6;
> +
> +    for ( unsigned int j = 0; j < 2; j++ )
> +    {
> +        /* First time is SPS, Second is PPS */
> +        if( j != 0 )
> +        {
> +            if (i_data_size < 1) {
> +                msg_Err( p_dec, "PPS too small after processing SPS %u", i_data_size );
 Cosmetics: "SPS/PPS" iof "SPS" (as it depends on the loop).
> +                goto error;
> +            }
> +            i_loop_end = p_buf[0];
 You could also always read the loop_end and simply do
 & (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*j+0] = 0;
> +            p_sys->p_sps_pps_buf[p_sys->i_sps_pps_size*j+1] = 0;
> +            p_sys->p_sps_pps_buf[p_sys->i_sps_pps_size*j+2] = 0;
> +            p_sys->p_sps_pps_buf[p_sys->i_sps_pps_size*j+3] = 1;
 Are you sure about the need for *j ? If it is an error you can use ++ to
simplify a bit.
> +
> +            p_sys->i_sps_pps_size += 4;
> +
> +            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;
> +}

Regards,

-- 
fenrir




More information about the vlc-devel mailing list