/*
-Copyright (c) 2003-2012, John Hurst
+Copyright (c) 2003-2018
+, John Hurst
All rights reserved.
Redistribution and use in source and binary forms, with or without
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*! \file asdcp-wrap.cpp
- \version $Id$
+ \version $Id$
\brief AS-DCP file manipulation utility
- This program wraps d-cinema essence (picture, sound or text) in t an AS-DCP
+ This program wraps d-cinema essence (picture, sound or text) into an AS-DCP
MXF file.
For more information about asdcplib, please refer to the header file AS_DCP.h
#include <KM_fileio.h>
#include <KM_prng.h>
+#include <AtmosSyncChannel_Mixer.h>
#include <AS_DCP.h>
#include <PCMParserList.h>
#include <Metadata.h>
const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
+const byte_t P_HFR_UL_2K[16] = {
+ 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d,
+ 0x0e, 0x16, 0x02, 0x02, 0x03, 0x01, 0x01, 0x03
+};
+
+const ASDCP::Dictionary *g_dict = 0;
+
//------------------------------------------------------------------------------------------
//
// command line option parser class
static byte_t default_ProductUUID_Data[UUIDlen] =
{ 0x7d, 0x83, 0x6e, 0x16, 0x37, 0xc7, 0x4c, 0x22,
0xb2, 0xe0, 0x46, 0xa7, 0x17, 0xe8, 0x4f, 0x42 };
-
+
memcpy(ProductUUID, default_ProductUUID_Data, UUIDlen);
CompanyName = "WidgetCo";
ProductName = "asdcp-wrap";
return; \
}
+//
+static void
+create_random_uuid(byte_t* uuidbuf)
+{
+ Kumu::UUID tmp_id;
+ GenRandomValue(tmp_id);
+ memcpy(uuidbuf, tmp_id.Value(), tmp_id.Size());
+}
+
//
void
banner(FILE* stream = stdout)
{
fprintf(stream, "\n\
%s (asdcplib %s)\n\n\
-Copyright (c) 2003-2012 John Hurst\n\n\
+Copyright (c) 2003-2018 John Hurst\n\n\
asdcplib may be copied only under the terms of the license found at\n\
the top of every file in the asdcplib distribution kit.\n\n\
Specify the -h (help) option for further information about %s\n\n",
\n\
%s [-3] [-a <uuid>] [-b <buffer-size>] [-C <UL>] [-d <duration>]\n\
[-e|-E] [-f <start-frame>] [-j <key-id-string>] [-k <key-string>]\n\
- [-l <label>] [-L] [-M] [-p <frame-rate>] [-s <num>] [-v] [-W]\n\
- [-z|-Z] <input-file>+ <output-file>\n\n",
+ [-l <label>] [-L] [-M] [-m <expr>] [-p <frame-rate>] [-s] [-v]\n\
+ [-W] [-z|-Z] <input-file>+ <output-file>\n\n",
PROGRAM_NAME, PROGRAM_NAME);
fprintf(stream, "\
Options:\n\
- -3 - With -c, create a stereoscopic image file. Expects two\n\
- directories of JP2K codestreams (directories must have\n\
- an equal number of frames; left eye is first).\n\
- - With -x, force stereoscopic interpretation of a JP2K\n\
- track file.\n\
- -C <UL> - Set ChannelAssignment UL value\n\
-h | -help - Show help\n\
-V - Show version information\n\
+ -3 - Create a stereoscopic image file. Expects two\n\
+ directories of JP2K codestreams (directories must have\n\
+ an equal number of frames; the left eye is first)\n\
+ -a <UUID> - Specify the Asset ID of the file\n\
+ -A <UL> - Set DataEssenceCoding UL value in an Aux Data file\n\
+ -b <buffer-size> - Specify size in bytes of picture frame buffer\n\
+ Defaults to 4,194,304 (4MB)\n\
+ -C <UL> - Set ChannelAssignment UL value in a PCM file\n\
+ -d <duration> - Number of frames to process, default all\n\
-e - Encrypt MPEG or JP2K headers (default)\n\
-E - Do not encrypt MPEG or JP2K headers\n\
+ -f <start-frame> - Starting frame number, default 0\n\
+ -g <rfc-5646-code>\n\
+ - Create MCA labels having the given RFC 5646 language code\n\
+ (requires option \"-m\")\n\
-j <key-id-str> - Write key ID instead of creating a random value\n\
-k <key-string> - Use key for ciphertext operations\n\
- -M - Do not create HMAC values when writing\n\
- -a <UUID> - Specify the Asset ID of a file (with -c)\n\
- -b <buffer-size> - Specify size in bytes of picture frame buffer.\n\
- Defaults to 4,194,304 (4MB)\n\
- -d <duration> - Number of frames to process, default all\n\
- -f <start-frame> - Starting frame number, default 0\n\
-l <label> - Use given channel format label when writing MXF sound\n\
- files. SMPTE 429-2 labels: '5.1', '6.1', '7.1', '7.1DS', 'WTF'.\n\
+ files. SMPTE 429-2 labels: '5.1', '6.1', '7.1',\n\
+ '7.1DS', 'WTF'\n\
Default is no label (valid for Interop only).\n\
-L - Write SMPTE UL values instead of MXF Interop\n\
+ -m <expr> - Write MCA labels using <expr>. Example:\n\
+ 51(L,R,C,LFE,Ls,Rs,),HI,VIN\n\
+ Note: The symbol '-' may be used for an unlabeled\n\
+ channel, but not within a soundfield.\n\
+ -M - Do not create HMAC values when writing\n\
-p <rate> - fps of picture when wrapping PCM or JP2K:\n\
Use one of [23|24|25|30|48|50|60], 24 is default\n\
+ -P <UL> - Set PictureEssenceCoding UL value in a JP2K file\n\
+ -s - Insert a Dolby Atmos synchronization channel when\n\
+ wrapping PCM. This implies a -L option(SMPTE ULs) and \n\
+ will overide -C and -l options with Configuration 4 \n\
+ Channel Assigment and no format label respectively. \n\
-v - Verbose, prints informative messages to stderr\n\
- -W - Read input file only, do not write source file\n\
+ -w - When writing 377-4 MCA labels, use the WTF Channel\n\
+ assignment label instead of the standard MCA label\n\
+ -W - Read input file only, do not write output file\n\
-z - Fail if j2c inputs have unequal parameters (default)\n\
-Z - Ignore unequal parameters in j2c inputs\n\
\n\
else if ( label_name == "6.1" )
return PCM::CF_CFG_2;
-
+
else if ( label_name == "7.1" )
return PCM::CF_CFG_3;
bool asset_id_flag; // true if an asset ID was given
bool encrypt_header_flag; // true if mpeg headers are to be encrypted
bool write_hmac; // true if HMAC values are to be generated and written
- /// bool read_hmac; // true if HMAC values are to be validated
- /// bool split_wav; // true if PCM is to be extracted to stereo WAV files
- /// bool mono_wav; // true if PCM is to be extracted to mono WAV files
bool verbose_flag; // true if the verbose option was selected
ui32_t fb_dump_size; // number of bytes of frame buffer to dump
- /// bool showindex_flag; // true if index is to be displayed
- /// bool showheader_flag; // true if MXF file header is to be displayed
bool no_write_flag; // true if no output files are to be written
bool version_flag; // true if the version display option was selected
bool help_flag; // true if the help display option was selected
bool stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
- /// ui32_t number_width; // number of digits in a serialized filename (for JPEG extract)
+ bool write_partial_pcm_flag; // if true, write the last frame of PCM input even when it is incomplete
ui32_t start_frame; // frame number to begin processing
ui32_t duration; // number of frames to be processed
bool use_smpte_labels; // if true, SMPTE UL values will be written instead of MXF Interop values
byte_t asset_id_value[UUIDlen];// value of asset ID (when asset_id_flag is true)
PCM::ChannelFormat_t channel_fmt; // audio channel arrangement
std::string out_file; //
- bool show_ul_values; /// if true, dump the UL table before going tp work.
+ bool show_ul_values_flag; /// if true, dump the UL table before going to work.
Kumu::PathList_t filenames; // list of filenames to be processed
UL channel_assignment;
+ UL picture_coding;
+ UL aux_data_coding;
+ bool dolby_atmos_sync_flag; // if true, insert a Dolby Atmos Synchronization channel.
+ ui32_t ffoa; // first frame of action for atmos wrapping
+ ui32_t max_channel_count; // max channel count for atmos wrapping
+ ui32_t max_object_count; // max object count for atmos wrapping
+ bool use_interop_sound_wtf; // make true to force WTF assignment label instead of MCA
+ ASDCP::MXF::ASDCP_MCAConfigParser mca_config;
+ std::string mca_language;
//
Rational PictureRate()
{
+ if ( picture_rate == 16 ) return EditRate_16;
+ if ( picture_rate == 18 ) return EditRate_18;
+ if ( picture_rate == 20 ) return EditRate_20;
+ if ( picture_rate == 22 ) return EditRate_22;
if ( picture_rate == 23 ) return EditRate_23_98;
if ( picture_rate == 24 ) return EditRate_24;
if ( picture_rate == 25 ) return EditRate_25;
if ( picture_rate == 96 ) return EditRate_96;
if ( picture_rate == 100 ) return EditRate_100;
if ( picture_rate == 120 ) return EditRate_120;
+ if ( picture_rate == 192 ) return EditRate_192;
+ if ( picture_rate == 200 ) return EditRate_200;
+ if ( picture_rate == 240 ) return EditRate_240;
return EditRate_24;
}
//
const char* szPictureRate()
{
+ if ( picture_rate == 16 ) return "16";
+ if ( picture_rate == 18 ) return "18.182";
+ if ( picture_rate == 20 ) return "20";
+ if ( picture_rate == 22 ) return "21.818";
if ( picture_rate == 23 ) return "23.976";
if ( picture_rate == 24 ) return "24";
if ( picture_rate == 25 ) return "25";
if ( picture_rate == 96 ) return "96";
if ( picture_rate == 100 ) return "100";
if ( picture_rate == 120 ) return "120";
+ if ( picture_rate == 192 ) return "192";
+ if ( picture_rate == 200 ) return "200";
+ if ( picture_rate == 240 ) return "240";
return "24";
}
encrypt_header_flag(true), write_hmac(true),
verbose_flag(false), fb_dump_size(0),
no_write_flag(false), version_flag(false), help_flag(false), stereo_image_flag(false),
- start_frame(0),
+ write_partial_pcm_flag(false), start_frame(0),
duration(0xffffffff), use_smpte_labels(false), j2c_pedantic(true),
fb_size(FRAME_BUFFER_SIZE),
channel_fmt(PCM::CF_NONE),
- show_ul_values(false)
+ ffoa(0), max_channel_count(10), max_object_count(118), // hard-coded sample atmos properties
+ dolby_atmos_sync_flag(false),
+ show_ul_values_flag(false),
+ mca_config(g_dict),
+ use_interop_sound_wtf(false)
{
memset(key_value, 0, KeyLen);
memset(key_id_value, 0, UUIDlen);
+ std::string mca_config_str;
for ( int i = 1; i < argc; i++ )
{
help_flag = true;
continue;
}
-
+
if ( argv[i][0] == '-'
&& ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
&& argv[i][2] == 0 )
{
case '3': stereo_image_flag = true; break;
+ case 'A':
+ TEST_EXTRA_ARG(i, 'A');
+ if ( ! aux_data_coding.DecodeHex(argv[i]) )
+ {
+ fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
+ return;
+ }
+ break;
+
case 'a':
asset_id_flag = true;
TEST_EXTRA_ARG(i, 'a');
case 'b':
TEST_EXTRA_ARG(i, 'b');
- fb_size = abs(atoi(argv[i]));
+ fb_size = Kumu::xabs(strtol(argv[i], 0, 10));
if ( verbose_flag )
fprintf(stderr, "Frame Buffer size: %u bytes.\n", fb_size);
break;
+ case 'C':
+ TEST_EXTRA_ARG(i, 'C');
+ if ( ! channel_assignment.DecodeHex(argv[i]) )
+ {
+ fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
+ return;
+ }
+ break;
+
case 'd':
TEST_EXTRA_ARG(i, 'd');
- duration = abs(atoi(argv[i]));
+ duration = Kumu::xabs(strtol(argv[i], 0, 10));
break;
case 'E': encrypt_header_flag = false; break;
case 'f':
TEST_EXTRA_ARG(i, 'f');
- start_frame = abs(atoi(argv[i]));
+ start_frame = Kumu::xabs(strtol(argv[i], 0, 10));
+ break;
+
+ case 'g':
+ TEST_EXTRA_ARG(i, 'g');
+ mca_language = argv[i];
break;
case 'h': help_flag = true; break;
case 'L': use_smpte_labels = true; break;
case 'M': write_hmac = false; break;
- case 'p':
- TEST_EXTRA_ARG(i, 'p');
- picture_rate = abs(atoi(argv[i]));
+ case 'm':
+ TEST_EXTRA_ARG(i, 'm');
+ mca_config_str = argv[i];
break;
- case 'U':
- TEST_EXTRA_ARG(i, 'U');
- if ( ! channel_assignment.DecodeHex(argv[i]) )
+ case 'P':
+ TEST_EXTRA_ARG(i, 'P');
+ if ( ! picture_coding.DecodeHex(argv[i]) )
{
fprintf(stderr, "Error decoding UL value: %s\n", argv[i]);
return;
}
break;
+ case 'p':
+ TEST_EXTRA_ARG(i, 'p');
+ picture_rate = Kumu::xabs(strtol(argv[i], 0, 10));
+ break;
+
+ case 's': dolby_atmos_sync_flag = true; break;
+ case 'u': show_ul_values_flag = true; break;
case 'V': version_flag = true; break;
case 'v': verbose_flag = true; break;
+ case 'w': use_interop_sound_wtf = true; break;
case 'W': no_write_flag = true; break;
+ case 'x': write_partial_pcm_flag = true; break;
case 'Z': j2c_pedantic = false; break;
case 'z': j2c_pedantic = true; break;
}
}
+ if ( ! mca_config_str.empty() )
+ {
+ if ( mca_language.empty() )
+ {
+ if ( ! mca_config.DecodeString(mca_config_str) )
+ {
+ return;
+ }
+ }
+ else
+ {
+ if ( ! mca_config.DecodeString(mca_config_str, mca_language) )
+ {
+ return;
+ }
+ }
+ }
+
if ( help_flag || version_flag )
- return;
-
+ {
+ return;
+ }
+
if ( filenames.size() < 2 )
{
fputs("Option requires at least two filename arguments: <input-file> <output-file>\n", stderr);
Kumu::FortunaRNG RNG;
// set up essence parser
- Result_t result = Parser.OpenRead(Options.filenames.front().c_str());
+ Result_t result = Parser.OpenRead(Options.filenames.front());
// set up MXF writer
if ( ASDCP_SUCCESS(result) )
Info.EncryptedEssence = true;
if ( Options.key_id_flag )
- memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
+ {
+ memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
+ }
else
- RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
+ {
+ create_random_uuid(Info.CryptographicKeyID);
+ }
Context = new AESEncContext;
result = Context->InitKey(Options.key_value);
}
if ( ASDCP_SUCCESS(result) )
- result = Writer.OpenWrite(Options.out_file.c_str(), Info, VDesc);
+ result = Writer.OpenWrite(Options.out_file, Info, VDesc);
}
if ( ASDCP_SUCCESS(result) )
while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
{
- if ( duration == 1 )
- {
result = Parser.ReadFrame(FrameBuffer);
if ( ASDCP_SUCCESS(result) )
{
if ( Options.verbose_flag )
FrameBuffer.Dump(stderr, Options.fb_dump_size);
-
+
if ( Options.encrypt_header_flag )
FrameBuffer.PlaintextOffset(0);
}
- }
if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
{
}
+//------------------------------------------------------------------------------------------
+
+// return false if an error is discovered
+bool
+check_phfr_params(CommandOptions& Options, JP2K::PictureDescriptor& PDesc)
+{
+ Rational rate = Options.PictureRate();
+ if ( rate != EditRate_96 && rate != EditRate_100 && rate != EditRate_120
+ && rate != EditRate_192 && rate != EditRate_200 && rate != EditRate_240 )
+ return true;
+
+ if ( PDesc.StoredWidth > 2048 )
+ {
+ fprintf(stderr, "P-HFR files currently limited to 2K.\n");
+ return false;
+ }
+
+ if ( ! Options.use_smpte_labels )
+ {
+ fprintf(stderr, "P-HFR files must be written using SMPTE labels. Use option '-L'.\n");
+ return false;
+ }
+
+ // do not set the label if the user has already done so
+ if ( ! Options.picture_coding.HasValue() )
+ Options.picture_coding = UL(P_HFR_UL_2K);
+
+ return true;
+}
+
//------------------------------------------------------------------------------------------
// JPEG 2000 essence
}
// set up essence parser
- Result_t result = ParserLeft.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
+ Result_t result = ParserLeft.OpenRead(Options.filenames.front(), Options.j2c_pedantic);
if ( ASDCP_SUCCESS(result) )
{
Options.filenames.pop_front();
- result = ParserRight.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
+ result = ParserRight.OpenRead(Options.filenames.front(), Options.j2c_pedantic);
}
// set up MXF writer
}
}
+ if ( ! check_phfr_params(Options, PDesc) )
+ return RESULT_FAIL;
+
if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
{
WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
Info.EncryptedEssence = true;
if ( Options.key_id_flag )
- memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
+ {
+ memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
+ }
else
- RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
+ {
+ create_random_uuid(Info.CryptographicKeyID);
+ }
Context = new AESEncContext;
result = Context->InitKey(Options.key_value);
}
if ( ASDCP_SUCCESS(result) )
- result = Writer.OpenWrite(Options.out_file.c_str(), Info, PDesc);
+ result = Writer.OpenWrite(Options.out_file, Info, PDesc);
+
+ if ( ASDCP_SUCCESS(result) && Options.picture_coding.HasValue() )
+ {
+ MXF::RGBAEssenceDescriptor *descriptor = 0;
+ Writer.OP1aHeader().GetMDObjectByType(g_dict->ul(MDD_RGBAEssenceDescriptor),
+ reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
+ descriptor->PictureEssenceCoding = Options.picture_coding;
+ }
}
if ( ASDCP_SUCCESS(result) )
{
if ( Options.verbose_flag )
FrameBuffer.Dump(stderr, Options.fb_dump_size);
-
+
if ( Options.encrypt_header_flag )
FrameBuffer.PlaintextOffset(0);
}
{
if ( Options.verbose_flag )
FrameBuffer.Dump(stderr, Options.fb_dump_size);
-
+
if ( Options.encrypt_header_flag )
FrameBuffer.PlaintextOffset(0);
}
Kumu::FortunaRNG RNG;
// set up essence parser
- Result_t result = Parser.OpenRead(Options.filenames.front().c_str(), Options.j2c_pedantic);
+ Result_t result = Parser.OpenRead(Options.filenames.front(), Options.j2c_pedantic);
// set up MXF writer
if ( ASDCP_SUCCESS(result) )
}
}
+ if ( ! check_phfr_params(Options, PDesc) )
+ return RESULT_FAIL;
+
if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
{
WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
Info.EncryptedEssence = true;
if ( Options.key_id_flag )
- memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
+ {
+ memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
+ }
else
- RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
+ {
+ create_random_uuid(Info.CryptographicKeyID);
+ }
Context = new AESEncContext;
result = Context->InitKey(Options.key_value);
}
if ( ASDCP_SUCCESS(result) )
- result = Writer.OpenWrite(Options.out_file.c_str(), Info, PDesc);
+ result = Writer.OpenWrite(Options.out_file, Info, PDesc);
+
+ if ( ASDCP_SUCCESS(result) && Options.picture_coding.HasValue() )
+ {
+ MXF::RGBAEssenceDescriptor *descriptor = 0;
+ Writer.OP1aHeader().GetMDObjectByType(g_dict->ul(MDD_RGBAEssenceDescriptor),
+ reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
+ descriptor->PictureEssenceCoding = Options.picture_coding;
+ }
}
if ( ASDCP_SUCCESS(result) )
while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
{
- if ( duration == 1 )
- {
result = Parser.ReadFrame(FrameBuffer);
if ( ASDCP_SUCCESS(result) )
{
if ( Options.verbose_flag )
FrameBuffer.Dump(stderr, Options.fb_dump_size);
-
+
if ( Options.encrypt_header_flag )
FrameBuffer.PlaintextOffset(0);
}
- }
if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
{
FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
ADesc.ChannelFormat = Options.channel_fmt;
- if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE)
+ if ( Options.use_smpte_labels && ADesc.ChannelFormat == PCM::CF_NONE && Options.mca_config.empty() )
{
- fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see option -l)\n");
+ fprintf(stderr, "ATTENTION! Writing SMPTE audio without ChannelAssignment property (see options -C, -l and -m)\n");
}
if ( Options.verbose_flag )
Info.EncryptedEssence = true;
if ( Options.key_id_flag )
- memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
+ {
+ memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
+ }
else
- RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
+ {
+ create_random_uuid(Info.CryptographicKeyID);
+ }
Context = new AESEncContext;
result = Context->InitKey(Options.key_value);
}
if ( ASDCP_SUCCESS(result) )
- result = Writer.OpenWrite(Options.out_file.c_str(), Info, ADesc);
+ result = Writer.OpenWrite(Options.out_file, Info, ADesc);
- if ( ASDCP_SUCCESS(result) && Options.channel_assignment.HasValue() )
+ if ( ASDCP_SUCCESS(result)
+ && ( Options.channel_assignment.HasValue()
+ || ! Options.mca_config.empty() ) )
{
- MXF::WaveAudioDescriptor *descriptor = 0;
- Writer.OPAtomHeader().GetMDObjectByType(DefaultSMPTEDict().ul(MDD_WaveAudioDescriptor),
- reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
- descriptor->ChannelAssignment = Options.channel_assignment;
+ MXF::WaveAudioDescriptor *essence_descriptor = 0;
+ Writer.OP1aHeader().GetMDObjectByType(g_dict->ul(MDD_WaveAudioDescriptor),
+ reinterpret_cast<MXF::InterchangeObject**>(&essence_descriptor));
+ assert(essence_descriptor);
+
+ if ( Options.mca_config.empty() )
+ {
+ essence_descriptor->ChannelAssignment = Options.channel_assignment;
+ }
+ else
+ {
+ if ( Options.mca_config.ChannelCount() != essence_descriptor->ChannelCount )
+ {
+ fprintf(stderr, "MCA label count (%d) differs from essence stream channel count (%d).\n",
+ Options.mca_config.ChannelCount(), essence_descriptor->ChannelCount);
+ return RESULT_FAIL;
+ }
+
+ if ( Options.channel_assignment.HasValue() )
+ {
+ essence_descriptor->ChannelAssignment = Options.channel_assignment;
+ }
+ else if ( Options.use_interop_sound_wtf )
+ {
+ essence_descriptor->ChannelAssignment = g_dict->ul(MDD_DCAudioChannelCfg_4_WTF);
+ }
+ else
+ {
+ essence_descriptor->ChannelAssignment = g_dict->ul(MDD_DCAudioChannelCfg_MCA);
+ }
+
+ // add descriptors to the essence_descriptor and header
+ ASDCP::MXF::InterchangeObject_list_t::iterator i;
+ for ( i = Options.mca_config.begin(); i != Options.mca_config.end(); ++i )
+ {
+ if ( (*i)->GetUL() != UL(g_dict->ul(MDD_AudioChannelLabelSubDescriptor))
+ && (*i)->GetUL() != UL(g_dict->ul(MDD_SoundfieldGroupLabelSubDescriptor))
+ && (*i)->GetUL() != UL(g_dict->ul(MDD_GroupOfSoundfieldGroupsLabelSubDescriptor)) )
+ {
+ fprintf(stderr, "Essence sub-descriptor is not an MCALabelSubDescriptor.\n");
+ (*i)->Dump();
+ }
+
+ Writer.OP1aHeader().AddChildObject(*i);
+ essence_descriptor->SubDescriptors.push_back((*i)->InstanceUID);
+ *i = 0; // parent will only free the ones we don't keep
+ }
+ }
}
}
{
fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
- result = RESULT_ENDOFFILE;
- continue;
+
+ if ( Options.write_partial_pcm_flag )
+ {
+ result = RESULT_ENDOFFILE;
+ continue;
+ }
}
if ( Options.verbose_flag )
return result;
}
+// Mix one or more plaintext PCM audio streams with a Dolby Atmos Synchronization channel and write them to a plaintext ASDCP file
+// Mix one or more plaintext PCM audio streams with a Dolby Atmos Synchronization channel and write them to a ciphertext ASDCP file
+//
+Result_t
+write_PCM_with_ATMOS_sync_file(CommandOptions& Options)
+{
+ AESEncContext* Context = 0;
+ HMACContext* HMAC = 0;
+ PCM::MXFWriter Writer;
+ PCM::FrameBuffer FrameBuffer;
+ PCM::AudioDescriptor ADesc;
+ Rational PictureRate = Options.PictureRate();
+ byte_t IV_buf[CBC_BLOCK_SIZE];
+ Kumu::FortunaRNG RNG;
+
+ WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
+ if ( Options.asset_id_flag )
+ memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
+ else
+ Kumu::GenRandomUUID(Info.AssetUUID);
+ AtmosSyncChannelMixer Mixer(Info.AssetUUID);
+
+ // set up essence parser
+ Result_t result = Mixer.OpenRead(Options.filenames, PictureRate);
+
+ // set up MXF writer
+ if ( ASDCP_SUCCESS(result) )
+ {
+ if ( Mixer.ChannelCount() % 2 != 0 )
+ {
+ result = Mixer.AppendSilenceChannels(1);
+ }
+
+ Mixer.FillAudioDescriptor(ADesc);
+
+ ADesc.EditRate = PictureRate;
+ FrameBuffer.Capacity(PCM::CalcFrameBufferSize(ADesc));
+ ADesc.ChannelFormat = PCM::CF_CFG_4;
+
+ if ( Options.verbose_flag )
+ {
+ fprintf(stderr, "%.1fkHz PCM Audio, %s fps (%u spf)\n",
+ ADesc.AudioSamplingRate.Quotient() / 1000.0,
+ Options.szPictureRate(),
+ PCM::CalcSamplesPerFrame(ADesc));
+ fputs("AudioDescriptor:\n", stderr);
+ PCM::AudioDescriptorDump(ADesc);
+ }
+ }
+
+ if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
+ {
+ Info.LabelSetType = LS_MXF_SMPTE;
+ fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
+
+ // configure encryption
+ if( Options.key_flag )
+ {
+ Kumu::GenRandomUUID(Info.ContextID);
+ Info.EncryptedEssence = true;
+
+ if ( Options.key_id_flag )
+ {
+ memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
+ }
+ else
+ {
+ create_random_uuid(Info.CryptographicKeyID);
+ }
+
+ Context = new AESEncContext;
+ result = Context->InitKey(Options.key_value);
+
+ if ( ASDCP_SUCCESS(result) )
+ result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
+
+ if ( ASDCP_SUCCESS(result) && Options.write_hmac )
+ {
+ Info.UsesHMAC = true;
+ HMAC = new HMACContext;
+ result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
+ }
+ }
+
+ if ( ASDCP_SUCCESS(result) )
+ result = Writer.OpenWrite(Options.out_file, Info, ADesc);
+ }
+
+ if ( ASDCP_SUCCESS(result) )
+ {
+ result = Mixer.Reset();
+ ui32_t duration = 0;
+
+ while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
+ {
+ result = Mixer.ReadFrame(FrameBuffer);
+
+ if ( ASDCP_SUCCESS(result) )
+ {
+ if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
+ {
+ fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
+ fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
+
+ if ( Options.write_partial_pcm_flag )
+ {
+ result = RESULT_ENDOFFILE;
+ continue;
+ }
+ }
+
+ if ( Options.verbose_flag )
+ FrameBuffer.Dump(stderr, Options.fb_dump_size);
+
+ if ( ! Options.no_write_flag )
+ {
+ result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
+
+ // The Writer class will forward the last block of ciphertext
+ // to the encryption context for use as the IV for the next
+ // frame. If you want to use non-sequitur IV values, un-comment
+ // the following line of code.
+ // if ( ASDCP_SUCCESS(result) && Options.key_flag )
+ // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
+ }
+ }
+ }
+
+ if ( result == RESULT_ENDOFFILE )
+ result = RESULT_OK;
+ }
+
+ if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
+ result = Writer.Finalize();
+
+ return result;
+}
+
//------------------------------------------------------------------------------------------
// TimedText essence
Kumu::FortunaRNG RNG;
// set up essence parser
- Result_t result = Parser.OpenRead(Options.filenames.front().c_str());
+ Result_t result = Parser.OpenRead(Options.filenames.front());
// set up MXF writer
if ( ASDCP_SUCCESS(result) )
else
Kumu::GenRandomUUID(Info.AssetUUID);
- if ( Options.use_smpte_labels )
- {
- Info.LabelSetType = LS_MXF_SMPTE;
- fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
- }
+ // 428-7 IN 429-5 always uses SMPTE labels
+ Info.LabelSetType = LS_MXF_SMPTE;
+ fprintf(stderr, "ATTENTION! Writing SMPTE Universal Labels\n");
// configure encryption
if( Options.key_flag )
Info.EncryptedEssence = true;
if ( Options.key_id_flag )
- memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
+ {
+ memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
+ }
else
- RNG.FillRandom(Info.CryptographicKeyID, UUIDlen);
+ {
+ create_random_uuid(Info.CryptographicKeyID);
+ }
Context = new AESEncContext;
result = Context->InitKey(Options.key_value);
}
if ( ASDCP_SUCCESS(result) )
- result = Writer.OpenWrite(Options.out_file.c_str(), Info, TDesc);
+ result = Writer.OpenWrite(Options.out_file, Info, TDesc);
}
if ( ASDCP_FAILURE(result) )
if ( ! Options.no_write_flag )
{
result = Writer.WriteAncillaryResource(FrameBuffer, Context, HMAC);
-
+
// The Writer class will forward the last block of ciphertext
// to the encryption context for use as the IV for the next
// frame. If you want to use non-sequitur IV values, un-comment
return result;
}
+// Write one or more plaintext Dolby ATMOS bytestreams to a plaintext ASDCP file
+// Write one or more plaintext Dolby ATMOS bytestreams to a ciphertext ASDCP file
+//
+Result_t
+write_dolby_atmos_file(CommandOptions& Options)
+{
+ AESEncContext* Context = 0;
+ HMACContext* HMAC = 0;
+ ATMOS::MXFWriter Writer;
+ DCData::FrameBuffer FrameBuffer(Options.fb_size);
+ ATMOS::AtmosDescriptor ADesc;
+ DCData::SequenceParser Parser;
+ byte_t IV_buf[CBC_BLOCK_SIZE];
+ Kumu::FortunaRNG RNG;
+
+ // set up essence parser
+ Result_t result = Parser.OpenRead(Options.filenames.front());
+
+ // set up MXF writer
+ if ( ASDCP_SUCCESS(result) )
+ {
+ Parser.FillDCDataDescriptor(ADesc);
+ ADesc.EditRate = Options.PictureRate();
+ // TODO: fill AtmosDescriptor
+ ADesc.FirstFrame = Options.ffoa;
+ ADesc.MaxChannelCount = Options.max_channel_count;
+ ADesc.MaxObjectCount = Options.max_object_count;
+ Kumu::GenRandomUUID(ADesc.AtmosID);
+ ADesc.AtmosVersion = 1;
+ if ( Options.verbose_flag )
+ {
+ fprintf(stderr, "Dolby ATMOS Data\n");
+ fputs("AtmosDescriptor:\n", stderr);
+ fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
+ ATMOS::AtmosDescriptorDump(ADesc);
+ }
+ }
+
+ if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
+ {
+ WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
+ if ( Options.asset_id_flag )
+ memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
+ else
+ Kumu::GenRandomUUID(Info.AssetUUID);
+
+ Info.LabelSetType = LS_MXF_SMPTE;
+
+ // configure encryption
+ if( Options.key_flag )
+ {
+ Kumu::GenRandomUUID(Info.ContextID);
+ Info.EncryptedEssence = true;
+
+ if ( Options.key_id_flag )
+ {
+ memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
+ }
+ else
+ {
+ create_random_uuid(Info.CryptographicKeyID);
+ }
+
+ Context = new AESEncContext;
+ result = Context->InitKey(Options.key_value);
+
+ if ( ASDCP_SUCCESS(result) )
+ result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
+
+ if ( ASDCP_SUCCESS(result) && Options.write_hmac )
+ {
+ Info.UsesHMAC = true;
+ HMAC = new HMACContext;
+ result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
+ }
+ }
+
+ if ( ASDCP_SUCCESS(result) )
+ result = Writer.OpenWrite(Options.out_file, Info, ADesc);
+ }
+
+ if ( ASDCP_SUCCESS(result) )
+ {
+ ui32_t duration = 0;
+ result = Parser.Reset();
+
+ while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
+ {
+ result = Parser.ReadFrame(FrameBuffer);
+
+ if ( ASDCP_SUCCESS(result) )
+ {
+ if ( Options.verbose_flag )
+ FrameBuffer.Dump(stderr, Options.fb_dump_size);
+
+ if ( Options.encrypt_header_flag )
+ FrameBuffer.PlaintextOffset(0);
+ }
+
+ if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
+ {
+ result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
+
+ // The Writer class will forward the last block of ciphertext
+ // to the encryption context for use as the IV for the next
+ // frame. If you want to use non-sequitur IV values, un-comment
+ // the following line of code.
+ // if ( ASDCP_SUCCESS(result) && Options.key_flag )
+ // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
+ }
+ }
+
+ if ( result == RESULT_ENDOFFILE )
+ result = RESULT_OK;
+ }
+
+ if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
+ result = Writer.Finalize();
+
+ return result;
+}
+
+// Write one or more plaintext Aux Data (ST 429-14) bytestreams to a plaintext ASDCP file
+// Write one or more plaintext Aux Data (ST 429-14) bytestreams to a ciphertext ASDCP file
+//
+Result_t
+write_aux_data_file(CommandOptions& Options)
+{
+ AESEncContext* Context = 0;
+ HMACContext* HMAC = 0;
+ DCData::MXFWriter Writer;
+ DCData::FrameBuffer FrameBuffer(Options.fb_size);
+ DCData::DCDataDescriptor DDesc;
+ DCData::SequenceParser Parser;
+ byte_t IV_buf[CBC_BLOCK_SIZE];
+ Kumu::FortunaRNG RNG;
+
+ // set up essence parser
+ Result_t result = Parser.OpenRead(Options.filenames.front());
+
+ // set up MXF writer
+ if ( ASDCP_SUCCESS(result) )
+ {
+ Parser.FillDCDataDescriptor(DDesc);
+ memcpy(DDesc.DataEssenceCoding, Options.aux_data_coding.Value(), Options.aux_data_coding.Size());
+ DDesc.EditRate = Options.PictureRate();
+
+ if ( Options.verbose_flag )
+ {
+ fprintf(stderr, "Aux Data\n");
+ fprintf(stderr, "Frame Buffer size: %u\n", Options.fb_size);
+ DCData::DCDataDescriptorDump(DDesc);
+ }
+ }
+
+ if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
+ {
+ WriterInfo Info = s_MyInfo; // fill in your favorite identifiers here
+ if ( Options.asset_id_flag )
+ memcpy(Info.AssetUUID, Options.asset_id_value, UUIDlen);
+ else
+ Kumu::GenRandomUUID(Info.AssetUUID);
+
+ Info.LabelSetType = LS_MXF_SMPTE;
+
+ // configure encryption
+ if( Options.key_flag )
+ {
+ Kumu::GenRandomUUID(Info.ContextID);
+ Info.EncryptedEssence = true;
+
+ if ( Options.key_id_flag )
+ {
+ memcpy(Info.CryptographicKeyID, Options.key_id_value, UUIDlen);
+ }
+ else
+ {
+ create_random_uuid(Info.CryptographicKeyID);
+ }
+
+ Context = new AESEncContext;
+ result = Context->InitKey(Options.key_value);
+
+ if ( ASDCP_SUCCESS(result) )
+ result = Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
+
+ if ( ASDCP_SUCCESS(result) && Options.write_hmac )
+ {
+ Info.UsesHMAC = true;
+ HMAC = new HMACContext;
+ result = HMAC->InitKey(Options.key_value, Info.LabelSetType);
+ }
+ }
+
+ if ( ASDCP_SUCCESS(result) )
+ result = Writer.OpenWrite(Options.out_file, Info, DDesc);
+ }
+
+ if ( ASDCP_SUCCESS(result) )
+ {
+ ui32_t duration = 0;
+ result = Parser.Reset();
+
+ while ( ASDCP_SUCCESS(result) && duration++ < Options.duration )
+ {
+ result = Parser.ReadFrame(FrameBuffer);
+
+ if ( ASDCP_SUCCESS(result) )
+ {
+ if ( Options.verbose_flag )
+ FrameBuffer.Dump(stderr, Options.fb_dump_size);
+
+ if ( Options.encrypt_header_flag )
+ FrameBuffer.PlaintextOffset(0);
+ }
+
+ if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
+ {
+ result = Writer.WriteFrame(FrameBuffer, Context, HMAC);
+
+ // The Writer class will forward the last block of ciphertext
+ // to the encryption context for use as the IV for the next
+ // frame. If you want to use non-sequitur IV values, un-comment
+ // the following line of code.
+ // if ( ASDCP_SUCCESS(result) && Options.key_flag )
+ // Context->SetIVec(RNG.FillRandom(IV_buf, CBC_BLOCK_SIZE));
+ }
+ }
+
+ if ( result == RESULT_ENDOFFILE )
+ result = RESULT_OK;
+ }
+
+ if ( ASDCP_SUCCESS(result) && ! Options.no_write_flag )
+ result = Writer.Finalize();
+
+ return result;
+}
+
//
int
main(int argc, const char** argv)
{
Result_t result = RESULT_OK;
char str_buf[64];
+ g_dict = &ASDCP::DefaultSMPTEDict();
+
CommandOptions Options(argc, argv);
if ( Options.version_flag )
if ( Options.help_flag )
usage();
- if ( Options.version_flag || Options.help_flag )
+ if ( Options.show_ul_values_flag )
+ {
+ if ( Options.use_smpte_labels )
+ DefaultSMPTEDict().Dump(stdout);
+ else
+ DefaultInteropDict().Dump(stdout);
+ }
+
+ if ( Options.version_flag || Options.help_flag || Options.show_ul_values_flag )
return 0;
if ( Options.error_flag )
return 3;
}
- if ( Options.show_ul_values )
- {
- if ( Options.use_smpte_labels )
- DefaultSMPTEDict().Dump(stdout);
- else
- DefaultInteropDict().Dump(stdout);
- }
-
EssenceType_t EssenceType;
- result = ASDCP::RawEssenceType(Options.filenames.front().c_str(), EssenceType);
+ result = ASDCP::RawEssenceType(Options.filenames.front(), EssenceType);
if ( ASDCP_SUCCESS(result) )
{
case ESS_PCM_24b_48k:
case ESS_PCM_24b_96k:
- result = write_PCM_file(Options);
+ if ( Options.dolby_atmos_sync_flag )
+ {
+ result = write_PCM_with_ATMOS_sync_file(Options);
+ }
+ else
+ {
+ result = write_PCM_file(Options);
+ }
break;
-
+
case ESS_TIMED_TEXT:
result = write_timed_text_file(Options);
break;
+ case ESS_DCDATA_DOLBY_ATMOS:
+ result = write_dolby_atmos_file(Options);
+ break;
+
+ case ESS_DCDATA_UNKNOWN:
+ if ( ! Options.aux_data_coding.HasValue() )
+ {
+ fprintf(stderr, "Option \"-A <UL>\" is required for Aux Data essence.\n");
+ return 3;
+ }
+ else
+ {
+ result = write_aux_data_file(Options);
+ }
+ break;
+
default:
fprintf(stderr, "%s: Unknown file type, not ASDCP-compatible essence.\n",
Options.filenames.front().c_str());