Commit ce2d6368 authored by Nicolas Bertrand's avatar Nicolas Bertrand Committed by Jean-Baptiste Kempf

DCP: new access-demux module for DigitalCinemaPackage

This version only support 1-Reel-per-track DCPs, like trailers
Signed-off-by: Jean-Baptiste Kempf's avatarJean-Baptiste Kempf <jb@videolan.org>
parent af3bbd8f
......@@ -12,6 +12,7 @@ Access:
New schemes for implicit (ftps) and explicit (ftpes) modes.
* MMS audio selection on split audio streams
* Blu-Ray: support overlays, preferred menu languages, region selection and navigation
* Digital Cinema Package support
Decoder:
* Partial support for Voxware MetaSound
......
......@@ -1917,6 +1917,23 @@ then
fi
AM_CONDITIONAL(HAVE_AVFOUNDATION, [test "${have_avfoundation}" != "no"])
dnl
dnl DCP plugin (using asdcplib)
dnl
AC_ARG_ENABLE(dcp,
AS_HELP_STRING([--enable-dcp],[Digital Cinema Package support using asdcplib (default auto)]))
have_asdcp="no"
AS_IF([test "x${enable_dcp}" != "no"], [
AC_LANG_PUSH(C++)
AC_CHECK_HEADERS( [[AS@&t@_DCP.h]],
[have_asdcp="yes"],
[AS_IF( [test "x${enable_dcp}" = "yes"],
[AC_MSG_ERROR( [ ASDCP library cannot be found (needed for dcp module). Either use --enable-dcp=no or install asdcp library: http://www.cinecert.com/asdcplib/download/] )])
])
AC_LANG_POP(C++)
])
AM_CONDITIONAL(HAVE_ASDCP, [test "${have_asdcp}" != "no"])
dnl
dnl Demux plugins
dnl
......
......@@ -75,6 +75,7 @@ $Id$
* dbus: D-Bus control interface
* dbus_screensaver: preventing the computer from suspending
* dc1394: IIDC (DCAM) FireWire input module
* dcp: Digital Cinema Package input
* ddummy: dummy decoder
* decklink: input module to read from a Blackmagic SDI card
* decklinkoutput: output module to write to Blackmagic SDI card
......
......@@ -9,6 +9,13 @@ AM_CPPFLAGS += -I$(srcdir)/access
libattachment_plugin_la_SOURCES = access/attachment.c
access_LTLIBRARIES += libattachment_plugin.la
libdcp_plugin_la_SOURCES = access/dcp/dcpparser.h access/dcp/dcp.cpp access/dcp/dcpparser.cpp
libdcp_plugin_la_CPPFLAGS = $(AM_CPPFLAGS)
libdcp_plugin_la_LIBADD = $(AM_LIBADD) -lasdcp
if HAVE_ASDCP
access_LTLIBRARIES += libdcp_plugin.la
endif
libfilesystem_plugin_la_SOURCES = access/fs.h access/file.c access/directory.c access/fs.c
libfilesystem_plugin_la_CPPFLAGS = $(AM_CPPFLAGS)
if HAVE_WIN32
......
/*****************************************************************************
* Copyright (C) 2012-2013 VLC authors and VideoLAN
*
* Authors:
* Nicolas Bertrand <nico@isf.cc>
* Simona-Marinela Prodea <simona dot marinela dot prodea at gmail dot com>
* Jean-Baptiste Kempf <jb@videolan.org>
* Pierre Villard <pierre dot villard dot fr at gmail dot com>
* Claire Etienne
* Aurélie Sbinné
* Samuel Kerjose
* Julien Puyobro
*
* 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.
*****************************************************************************/
/**
* @file dcp.cpp
* @brief DCP access-demux module for Digital Cinema Packages using asdcp library
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#define __STDC_CONSTANT_MACROS 1
/* VLC core API headers */
#include <vlc_common.h>
#include <vlc_demux.h>
#include <vlc_plugin.h>
#include <vlc_xml.h>
#include <vlc_url.h>
#include <vlc_aout.h>
/* ASDCP headers */
#include <AS_DCP.h>
#include "dcpparser.h"
using namespace ASDCP;
using namespace std;
#define FRAME_BUFFER_SIZE 1302083 /* maximum frame length, in bytes, after
"Digital Cinema System Specification Version 1.2
with Errata as of 30 August 2012" */
/* Forward declarations */
static int Open( vlc_object_t * );
static void Close( vlc_object_t * );
/* Module descriptor */
vlc_module_begin()
set_shortname( N_( "DCP" ) )
add_shortcut( "dcp" )
set_description( N_( "Digital Cinema Package module" ) )
set_capability( "access_demux", 0 )
set_category( CAT_INPUT )
set_subcategory( SUBCAT_INPUT_ACCESS )
set_callbacks( Open, Close )
vlc_module_end()
//! Kind of MXF MEDIA TYPE
typedef enum MxfMedia_t {
MXF_UNKNOWN = 0,
MXF_PICTURE,
MXF_AUDIO,
} MxfMedia_t;
/* ASDCP library (version 1.10.48) can handle files having one of the following Essence Types, as defined in AS_DCP.h:
ESS_UNKNOWN, // the file is not a supported AS-DCP essence container
ESS_MPEG2_VES, // the file contains an MPEG video elementary stream
ESS_JPEG_2000, // the file contains one or more JPEG 2000 codestreams
ESS_PCM_24b_48k, // the file contains one or more PCM audio pairs
ESS_PCM_24b_96k, // the file contains one or more PCM audio pairs
ESS_TIMED_TEXT, // the file contains an XML timed text document and one or more resources
ESS_JPEG_2000_S, // the file contains one or more JPEG 2000 codestream pairs (stereoscopic).
The classes for handling these essence types are defined in AS_DCP.h and are different for each essence type, respectively. The demux_sys_t structure contains members for handling each of these essence types.
*/
class demux_sys_t
{
public:
/* ASDCP Picture Essence Type */
EssenceType_t PictureEssType;
/* ASDCP Video MXF Reader */
union
{
/* JPEG2000 essence type */
JP2K::MXFReader *p_PicMXFReader;
/* JPEG2000 stereoscopic essence type */
JP2K::MXFSReader *p_PicMXFSReader;
/* MPEG2 essence type */
MPEG2::MXFReader *p_VideoMXFReader;
};
/* ASDCP Audio MXF Reader */
PCM::MXFReader *p_AudioMXFReader;
/* audio buffer size */
uint32_t i_audio_buffer;
/* elementary streams */
es_out_id_t *p_video_es;
es_out_id_t *p_audio_es;
/* DCP object */
dcp_t *p_dcp;
/* current frame number */
uint32_t frame_no;
/* frame rate */
unsigned int frame_rate_num;
unsigned int frame_rate_denom;
/* total number of frames */
uint32_t frames_total;
uint8_t i_chans_to_reorder; /* do we need channel reordering */
uint8_t pi_chan_table[AOUT_CHAN_MAX];
uint8_t i_channels;
mtime_t i_pts;
demux_sys_t():
PictureEssType ( ESS_UNKNOWN ),
p_PicMXFReader( NULL ),
p_AudioMXFReader( NULL ),
p_video_es( NULL ),
p_audio_es( NULL ),
p_dcp( NULL ),
frame_no( 0 ) {};
~demux_sys_t()
{
switch ( PictureEssType )
{
case ESS_UNKNOWN:
break;
case ESS_JPEG_2000:
delete p_PicMXFReader;
break;
case ESS_JPEG_2000_S:
delete p_PicMXFSReader;
break;
case ESS_MPEG2_VES:
delete p_VideoMXFReader;
break;
default:
break;
}
delete p_AudioMXFReader;
delete p_dcp;
}
};
/*TODO: basic correlation between SMPTE S428-3/S429-2
* Real sound is more complex with case of left/right surround, ...
* and hearing impaired/Narration channels */
/* 1 channel: mono */
static const uint32_t i_channels_1[] =
{ AOUT_CHAN_LEFT, 0 };
/* 2 channels: stereo */
static const uint32_t i_channels_2[]=
{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT, 0 };
/* 4 channels */
static const uint32_t i_channels_4[] =
{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT, AOUT_CHAN_CENTER,
AOUT_CHAN_LFE, 0 };
/* 6 channels: 5.1 */
static const uint32_t i_channels_6[] =
{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT, AOUT_CHAN_CENTER,
AOUT_CHAN_LFE, AOUT_CHAN_REARLEFT, AOUT_CHAN_REARRIGHT,
0 };
/* 7 channels: 6.1 */
static const uint32_t i_channels_7[] =
{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT, AOUT_CHAN_CENTER,
AOUT_CHAN_LFE, AOUT_CHAN_REARLEFT, AOUT_CHAN_REARRIGHT,
AOUT_CHAN_REARCENTER, 0 };
/* 8 channels: 7.1 */
static const uint32_t i_channels_8[] =
{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT, AOUT_CHAN_CENTER,
AOUT_CHAN_LFE, AOUT_CHAN_REARLEFT, AOUT_CHAN_REARRIGHT,
AOUT_CHAN_MIDDLELEFT, AOUT_CHAN_MIDDLERIGHT, 0 };
/* 9 channels; 8.1 */
static const uint32_t i_channels_9[] =
{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT, AOUT_CHAN_CENTER,
AOUT_CHAN_LFE, AOUT_CHAN_REARLEFT, AOUT_CHAN_REARRIGHT,
AOUT_CHAN_MIDDLELEFT, AOUT_CHAN_MIDDLERIGHT, AOUT_CHAN_REARCENTER, 0 };
static const uint32_t *pi_channels_aout [] =
{ NULL,
i_channels_1,
i_channels_2,
NULL,
i_channels_4,
NULL,
i_channels_6,
i_channels_7,
i_channels_8,
i_channels_9 };
static const unsigned i_channel_mask[] =
{ 0,
AOUT_CHAN_LEFT,
AOUT_CHANS_STEREO,
0,
AOUT_CHANS_3_1,
0,
AOUT_CHANS_5_1,
AOUT_CHANS_6_1_MIDDLE,
AOUT_CHANS_7_1,
AOUT_CHANS_8_1 };
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static int Demux( demux_t * );
static int Control( demux_t *, int, va_list );
int dcpInit ( demux_t *p_demux );
int parseXML ( demux_t * p_demux );
static inline void fillVideoFmt(
video_format_t * fmt, unsigned int width, unsigned int height,
unsigned int frame_rate_num, unsigned int frame_rate_denom );
void CloseDcpAndMxf( demux_t *p_demux );
/*****************************************************************************
* Open: module init function
*****************************************************************************/
static int Open( vlc_object_t *obj )
{
demux_t *p_demux = ( demux_t* ) obj;
demux_sys_t *p_sys;
es_format_t video_format, audio_format;
int retval;
if( !p_demux->psz_file )
return VLC_EGENERIC;
p_sys = new ( nothrow ) demux_sys_t();
if( unlikely( p_sys == NULL ) ) {
return VLC_ENOMEM;
}
p_demux->p_sys = p_sys;
/* Allocate DCP object */
dcp_t *p_dcp = new ( nothrow ) dcp_t;
if( unlikely( p_dcp == NULL ) ) {
delete p_sys;
return VLC_ENOMEM;
}
p_sys->p_dcp = p_dcp;
/* handle the DCP directory, saving the paths for audio and video file, returning error if unsuccessful */
if( ( retval = dcpInit( p_demux ) ) )
goto error;
/* Open video file */
EssenceType( p_sys->p_dcp->videofile.c_str(), p_sys->PictureEssType );
switch( p_sys->PictureEssType )
{
case ESS_UNKNOWN:
msg_Err( p_demux, "The file %s is not a supported AS_DCP essence container", p_sys->p_dcp->videofile.c_str() );
retval = VLC_EGENERIC;
goto error;
case ESS_JPEG_2000:
case ESS_JPEG_2000_S: {
JP2K::PictureDescriptor PicDesc;
if (p_sys->PictureEssType == ESS_JPEG_2000_S) { /* 3D JPEG2000 */
JP2K::MXFSReader * p_PicMXFSReader = new ( nothrow ) JP2K::MXFSReader();
if( !p_PicMXFSReader) {
retval = VLC_ENOMEM;
goto error;
}
if( !ASDCP_SUCCESS( p_PicMXFSReader->OpenRead( p_sys->p_dcp->videofile.c_str() ) ) ) {
msg_Err( p_demux, "File %s could not be opened with ASDCP", p_sys->p_dcp->videofile.c_str() );
retval = VLC_EGENERIC;
delete p_PicMXFSReader;
goto error;
}
p_PicMXFSReader->FillPictureDescriptor( PicDesc );
p_sys->p_PicMXFSReader = p_PicMXFSReader;
} else { /* 2D JPEG2000 */
JP2K::MXFReader *p_PicMXFReader = new ( nothrow ) JP2K::MXFReader();
if( !p_PicMXFReader ) {
retval = VLC_ENOMEM;
goto error;
}
if( !ASDCP_SUCCESS( p_PicMXFReader->OpenRead( p_sys->p_dcp->videofile.c_str() ) ) ) {
msg_Err( p_demux, "File %s could not be opened with ASDCP",
p_sys->p_dcp->videofile.c_str() );
retval = VLC_EGENERIC;
delete p_PicMXFReader;
goto error;
}
p_PicMXFReader->FillPictureDescriptor( PicDesc );
p_sys->p_PicMXFReader = p_PicMXFReader;
}
es_format_Init( &video_format, VIDEO_ES, VLC_CODEC_JPEG2000 );
fillVideoFmt( &video_format.video, PicDesc.StoredWidth, PicDesc.StoredHeight,
PicDesc.EditRate.Numerator, PicDesc.EditRate.Denominator );
p_sys->frame_rate_num = PicDesc.EditRate.Numerator;
p_sys->frame_rate_denom = PicDesc.EditRate.Denominator;
p_sys->frames_total = PicDesc.ContainerDuration;
break;
}
case ESS_MPEG2_VES: {
MPEG2::MXFReader *p_VideoMXFReader = p_sys->p_VideoMXFReader = new ( nothrow ) MPEG2::MXFReader();
MPEG2::VideoDescriptor VideoDesc;
if( !p_VideoMXFReader ) {
retval = VLC_ENOMEM;
goto error;
}
if( !ASDCP_SUCCESS( p_VideoMXFReader->OpenRead( p_sys->p_dcp->videofile.c_str() ) ) ) {
msg_Err( p_demux, "File %s could not be opened with ASDCP", p_sys->p_dcp->videofile.c_str() );
retval = VLC_EGENERIC;
goto error;
}
p_VideoMXFReader->FillVideoDescriptor( VideoDesc );
es_format_Init( &video_format, VIDEO_ES, VLC_CODEC_MPGV );
fillVideoFmt( &video_format.video, VideoDesc.StoredWidth, VideoDesc.StoredHeight,
VideoDesc.EditRate.Numerator, VideoDesc.EditRate.Denominator );
p_sys->frame_rate_num = VideoDesc.EditRate.Numerator;
p_sys->frame_rate_denom = VideoDesc.EditRate.Denominator;
p_sys->frames_total = VideoDesc.ContainerDuration;
break;
}
default:
msg_Err( p_demux, "Unrecognized video format" );
retval = VLC_EGENERIC;
goto error;
}
if ( (p_sys->frame_rate_num == 0) || (p_sys->frame_rate_denom == 0) ) {
msg_Err(p_demux, "Invalid frame rate (%i/%i)",
p_sys->frame_rate_num, p_sys->frame_rate_denom);
retval = VLC_EGENERIC;
goto error;
}
if( ( p_sys->p_video_es = es_out_Add( p_demux->out, &video_format ) ) == NULL ) {
msg_Err( p_demux, "Failed to add video es" );
retval = VLC_EGENERIC;
goto error;
}
/* Open audio file */
EssenceType_t AudioEssType;
EssenceType( p_sys->p_dcp->audiofile.c_str(), AudioEssType );
if ( (AudioEssType == ESS_PCM_24b_48k) || (AudioEssType == ESS_PCM_24b_96k) ) {
PCM::MXFReader *p_AudioMXFReader = new ( nothrow ) PCM::MXFReader();
PCM::AudioDescriptor AudioDesc;
if( !p_AudioMXFReader ) {
retval = VLC_ENOMEM;
goto error;
}
if( !ASDCP_SUCCESS( p_AudioMXFReader->OpenRead( p_sys->p_dcp->audiofile.c_str() ) ) ) {
msg_Err( p_demux, "File %s could not be opened with ASDCP",
p_sys->p_dcp->audiofile.c_str() );
retval = VLC_EGENERIC;
delete p_AudioMXFReader;
goto error;
}
p_AudioMXFReader->FillAudioDescriptor( AudioDesc );
if ( (AudioDesc.ChannelCount >= sizeof(pi_channels_aout)/sizeof(uint32_t *))
|| (pi_channels_aout[AudioDesc.ChannelCount] == NULL) ){
msg_Err(p_demux, " DCP module does not support %i channels",
AudioDesc.ChannelCount);
retval = VLC_EGENERIC;
delete p_AudioMXFReader;
goto error;
} else {
es_format_Init( &audio_format, AUDIO_ES, VLC_CODEC_S24L );
if( AudioDesc.AudioSamplingRate.Denominator != 0 )
audio_format.audio.i_rate =
AudioDesc.AudioSamplingRate.Numerator
/ AudioDesc.AudioSamplingRate.Denominator;
else if ( AudioEssType == ESS_PCM_24b_96k )
audio_format.audio.i_rate = 96000;
else
audio_format.audio.i_rate = 48000;
p_sys->i_audio_buffer = PCM::CalcFrameBufferSize(AudioDesc);
if (p_sys->i_audio_buffer == 0) {
msg_Err( p_demux, "Failed to get audio buffer size" );
retval = VLC_EGENERIC;
delete p_AudioMXFReader;
goto error;
}
audio_format.audio.i_bitspersample = AudioDesc.QuantizationBits;
audio_format.audio.i_blockalign = AudioDesc.BlockAlign;
audio_format.audio.i_channels =
p_sys->i_channels = AudioDesc.ChannelCount;
/* Manage channel orders */
p_sys->i_chans_to_reorder = aout_CheckChannelReorder(
pi_channels_aout[AudioDesc.ChannelCount], NULL,
i_channel_mask[AudioDesc.ChannelCount], p_sys->pi_chan_table );
if( ( p_sys->p_audio_es = es_out_Add( p_demux->out, &audio_format ) ) == NULL ) {
msg_Err( p_demux, "Failed to add audio es" );
retval = VLC_EGENERIC;
delete p_AudioMXFReader;
goto error;
}
p_sys->p_AudioMXFReader = p_AudioMXFReader;
}
} else {
msg_Err( p_demux, "The file %s is not a supported AS_DCP essence container",
p_sys->p_dcp->audiofile.c_str() );
retval = VLC_EGENERIC;
goto error;
}
p_demux->pf_demux = Demux;
p_demux->pf_control = Control;
return VLC_SUCCESS;
error:
CloseDcpAndMxf( p_demux );
return retval;
}
/*****************************************************************************
* Close: module destroy function
*****************************************************************************/
static inline void Close( vlc_object_t *obj )
{
demux_t *p_demux = ( demux_t* ) obj;
CloseDcpAndMxf( p_demux );
}
/*****************************************************************************
* Demux: DCP Demuxing function
*****************************************************************************/
static int Demux( demux_t *p_demux )
{
demux_sys_t *p_sys = p_demux->p_sys;
block_t *p_video_frame = NULL, *p_audio_frame = NULL;
uint32_t i = p_sys->frame_no;
PCM::FrameBuffer AudioFrameBuff( p_sys->i_audio_buffer);
if( i == p_sys->frames_total )
return 0;
/* video frame */
switch( p_sys->PictureEssType )
{
case ESS_JPEG_2000:
case ESS_JPEG_2000_S:{
JP2K::FrameBuffer PicFrameBuff(FRAME_BUFFER_SIZE);
if ( ( p_video_frame = block_Alloc( FRAME_BUFFER_SIZE )) == NULL )
goto error;
if ( ! ASDCP_SUCCESS(
PicFrameBuff.SetData(p_video_frame->p_buffer, FRAME_BUFFER_SIZE)) )
goto error_asdcp;
if ( p_sys->PictureEssType == ESS_JPEG_2000_S ) {
if ( ! ASDCP_SUCCESS(
p_sys->p_PicMXFSReader->ReadFrame(i + p_sys->p_dcp->i_video_entry, JP2K::SP_LEFT, PicFrameBuff, 0, 0)) ) {
PicFrameBuff.SetData(0,0);
goto error_asdcp;
}
} else {
if ( ! ASDCP_SUCCESS(
p_sys->p_PicMXFReader->ReadFrame(i + p_sys->p_dcp->i_video_entry, PicFrameBuff, 0, 0)) ) {
PicFrameBuff.SetData(0,0);
goto error_asdcp;
}
}
p_video_frame->i_buffer = PicFrameBuff.Size();
break;
}
case ESS_MPEG2_VES: {
MPEG2::FrameBuffer VideoFrameBuff(FRAME_BUFFER_SIZE);
if ( ( p_video_frame = block_Alloc( FRAME_BUFFER_SIZE )) == NULL )
goto error;
if ( ! ASDCP_SUCCESS(
VideoFrameBuff.SetData(p_video_frame->p_buffer, FRAME_BUFFER_SIZE)) )
goto error_asdcp;
if ( ! ASDCP_SUCCESS(
p_sys->p_VideoMXFReader->ReadFrame(i + p_sys->p_dcp->i_video_entry, VideoFrameBuff, 0, 0)) ) {
VideoFrameBuff.SetData(0,0);
goto error_asdcp;
}
p_video_frame->i_buffer = VideoFrameBuff.Size();
break;
}
default:
msg_Err( p_demux, "Unrecognized video format" );
goto error;
}
p_video_frame->i_length = CLOCK_FREQ * p_sys->frame_rate_denom / p_sys->frame_rate_num;
p_video_frame->i_pts = CLOCK_FREQ * p_sys->frame_no * p_sys->frame_rate_denom / p_sys->frame_rate_num;
/* audio frame */
if ( ( p_audio_frame = block_Alloc( p_sys->i_audio_buffer )) == NULL ) {
goto error;
}
if ( ! ASDCP_SUCCESS(
AudioFrameBuff.SetData(p_audio_frame->p_buffer, p_sys->i_audio_buffer)) ) {
goto error_asdcp;
}
if ( ! ASDCP_SUCCESS(
p_sys->p_AudioMXFReader->ReadFrame(i + p_sys->p_dcp->i_audio_entry, AudioFrameBuff, 0, 0)) ) {
AudioFrameBuff.SetData(0,0);
goto error_asdcp;
}
if( p_sys->i_chans_to_reorder )
aout_ChannelReorder( p_audio_frame->p_buffer, p_audio_frame->i_buffe