/*---------------------------------------------------------------------- | AP4_StsdAtom::AP4_StsdAtom +---------------------------------------------------------------------*/ AP4_StsdAtom::AP4_StsdAtom(AP4_Size size, AP4_ByteStream& stream, AP4_AtomFactory& atom_factory) : AP4_ContainerAtom(AP4_ATOM_TYPE_STSD, size, true, stream) { // read the number of entries AP4_UI32 entry_count; stream.ReadUI32(entry_count); // read all entries AP4_Size bytes_available = size-AP4_FULL_ATOM_HEADER_SIZE-4; m_Data.SetDataSize(bytes_available); stream.Read(m_Data.UseData(), m_Data.GetDataSize()); AP4_ByteStream* s = DNew AP4_MemoryByteStream(m_Data.UseData(), m_Data.GetDataSize()); for (unsigned int i=0; i<entry_count; i++) { AP4_Atom* atom; if (AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(*s, bytes_available, atom, this))) { atom->SetParent(this); m_Children.Add(atom); } } s->Release(); // initialize the sample description cache m_SampleDescriptions.EnsureCapacity(m_Children.ItemCount()); for (AP4_Ordinal i=0; i<m_Children.ItemCount(); i++) { m_SampleDescriptions.Append(NULL); } }
/*---------------------------------------------------------------------- | AP4_File::AP4_File +---------------------------------------------------------------------*/ AP4_File::AP4_File(AP4_ByteStream& stream, AP4_AtomFactory& atom_factory) : m_Movie(NULL) { // get all atoms AP4_Atom* atom; while (AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(stream, atom))) { switch (atom->GetType()) { case AP4_ATOM_TYPE_MOOV: m_Movie = new AP4_Movie(dynamic_cast<AP4_MoovAtom*>(atom), stream); break; case AP4_ATOM_TYPE_MOOF: if (m_Movie) { m_Movie->ProcessMoof(AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom), stream); } delete atom; break; case AP4_ATOM_TYPE_FTYP: //m_Movie = new AP4_Movie(dynamic_cast<AP4_FtypAtom*>(atom), stream); m_FileType = dynamic_cast<AP4_FtypAtom*>(atom); default: m_OtherAtoms.Add(atom); } } }
/*---------------------------------------------------------------------- | AP4_File::AP4_File +---------------------------------------------------------------------*/ AP4_File::AP4_File(AP4_ByteStream& stream, AP4_AtomFactory& atom_factory, bool moov_only) : m_Movie(NULL), m_FileType(NULL), m_MetaData(NULL), m_MoovIsBeforeMdat(true) { // parse top-level atoms AP4_Atom* atom; AP4_Position stream_position; bool keep_parsing = true; while (keep_parsing && AP4_SUCCEEDED(stream.Tell(stream_position)) && AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(stream, atom))) { AddChild(atom); switch (atom->GetType()) { case AP4_ATOM_TYPE_MOOV: m_Movie = new AP4_Movie(AP4_DYNAMIC_CAST(AP4_MoovAtom, atom), stream, false); if (moov_only) keep_parsing = false; break; case AP4_ATOM_TYPE_FTYP: m_FileType = AP4_DYNAMIC_CAST(AP4_FtypAtom, atom); break; case AP4_ATOM_TYPE_MDAT: // see if we are before the moov atom if (m_Movie == NULL) m_MoovIsBeforeMdat = false; break; } } }
/*---------------------------------------------------------------------- | AP4_StsdAtom::AP4_StsdAtom +---------------------------------------------------------------------*/ AP4_StsdAtom::AP4_StsdAtom(AP4_UI32 size, AP4_UI08 version, AP4_UI32 flags, AP4_ByteStream& stream, AP4_AtomFactory& atom_factory) : AP4_ContainerAtom(AP4_ATOM_TYPE_STSD, size, false, version, flags) { // read the number of entries AP4_UI32 entry_count; stream.ReadUI32(entry_count); // save and switch the factory's context atom_factory.PushContext(m_Type); // read all entries AP4_LargeSize bytes_available = size-AP4_FULL_ATOM_HEADER_SIZE-4; for (unsigned int i=0; i<entry_count; i++) { AP4_Atom* atom; if (AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(stream, bytes_available, atom))) { atom->SetParent(this); m_Children.Add(atom); } } // restore the saved context atom_factory.PopContext(); // initialize the sample description cache m_SampleDescriptions.EnsureCapacity(m_Children.ItemCount()); for (AP4_Ordinal i=0; i<m_Children.ItemCount(); i++) { m_SampleDescriptions.Append(NULL); } }
/*---------------------------------------------------------------------- | AP4_LinearReader::AdvanceFragment +---------------------------------------------------------------------*/ AP4_Result AP4_LinearReader::AdvanceFragment() { AP4_Result result; // go the the start of the next fragment result = m_FragmentStream->Seek(m_NextFragmentPosition); if (AP4_FAILED(result)) return result; // read atoms until we find a moof assert(m_HasFragments); if (!m_FragmentStream) return AP4_ERROR_INVALID_STATE; do { AP4_Atom* atom = NULL; result = AP4_DefaultAtomFactory::Instance.CreateAtomFromStream(*m_FragmentStream, atom); if (AP4_SUCCEEDED(result)) { if (atom->GetType() == AP4_ATOM_TYPE_MOOF) { AP4_ContainerAtom* moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); if (moof) { // remember where we are in the stream AP4_Position position = 0; m_FragmentStream->Tell(position); // process the movie fragment result = ProcessMoof(moof, position-atom->GetSize(), position+8); if (AP4_FAILED(result)) return result; // compute where the next fragment will be AP4_UI32 size; AP4_UI32 type; m_FragmentStream->Tell(position); result = m_FragmentStream->ReadUI32(size); if (AP4_FAILED(result)) return AP4_SUCCESS; // can't read more result = m_FragmentStream->ReadUI32(type); if (AP4_FAILED(result)) return AP4_SUCCESS; // can't read more if (size == 0) { m_NextFragmentPosition = 0; } else if (size == 1) { AP4_UI64 size_64 = 0; result = m_FragmentStream->ReadUI64(size_64); if (AP4_FAILED(result)) return AP4_SUCCESS; // can't read more m_NextFragmentPosition = position+size_64; } else { m_NextFragmentPosition = position+size; } return AP4_SUCCESS; } else { delete atom; } } else { delete atom; } } } while (AP4_SUCCEEDED(result)); return AP4_ERROR_EOS; }
/*---------------------------------------------------------------------- | AP4_OmaDcfAtomDecrypter::DecryptAtoms +---------------------------------------------------------------------*/ AP4_Result AP4_OmaDcfAtomDecrypter::DecryptAtoms(AP4_AtomParent& atoms, AP4_Processor::ProgressListener* /*listener*/, AP4_BlockCipherFactory* block_cipher_factory, AP4_ProtectionKeyMap& key_map) { // default factory if (block_cipher_factory == NULL) { block_cipher_factory = &AP4_DefaultBlockCipherFactory::Instance; } unsigned int index = 1; for (AP4_List<AP4_Atom>::Item* item = atoms.GetChildren().FirstItem(); item; item = item->GetNext()) { AP4_Atom* atom = item->GetData(); if (atom->GetType() != AP4_ATOM_TYPE_ODRM) continue; // check that we have the key const AP4_DataBuffer* key = key_map.GetKey(index++); if (key == NULL) return AP4_ERROR_INVALID_PARAMETERS; // check that we have all the atoms we need AP4_ContainerAtom* odrm = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); if (odrm == NULL) continue; // not enough info AP4_OdheAtom* odhe = AP4_DYNAMIC_CAST(AP4_OdheAtom, odrm->GetChild(AP4_ATOM_TYPE_ODHE)); if (odhe == NULL) continue; // not enough info AP4_OddaAtom* odda = AP4_DYNAMIC_CAST(AP4_OddaAtom, odrm->GetChild(AP4_ATOM_TYPE_ODDA));; if (odda == NULL) continue; // not enough info AP4_OhdrAtom* ohdr = AP4_DYNAMIC_CAST(AP4_OhdrAtom, odhe->GetChild(AP4_ATOM_TYPE_OHDR)); if (ohdr == NULL) continue; // not enough info // do nothing if the atom is not encrypted if (ohdr->GetEncryptionMethod() == AP4_OMA_DCF_ENCRYPTION_METHOD_NULL) { continue; } // create the byte stream AP4_ByteStream* cipher_stream = NULL; AP4_Result result = CreateDecryptingStream(*odrm, key->GetData(), key->GetDataSize(), block_cipher_factory, cipher_stream); if (AP4_SUCCEEDED(result)) { // replace the odda atom's payload with the decrypted stream odda->SetEncryptedPayload(*cipher_stream, ohdr->GetPlaintextLength()); cipher_stream->Release(); // the atom will now be in the clear ohdr->SetEncryptionMethod(AP4_OMA_DCF_ENCRYPTION_METHOD_NULL); ohdr->SetPaddingScheme(AP4_OMA_DCF_PADDING_SCHEME_NONE); } } return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | AP4_ContainerAtom::ReadChildren +---------------------------------------------------------------------*/ void AP4_ContainerAtom::ReadChildren(AP4_AtomFactory& atom_factory, AP4_ByteStream& stream, AP4_Size size) { AP4_Atom* atom; AP4_Size bytes_available = size; while (AP4_SUCCEEDED( atom_factory.CreateAtomFromStream(stream, bytes_available, atom, this))) { atom->SetParent(this); m_Children.Add(atom); } }
/*---------------------------------------------------------------------- | main +---------------------------------------------------------------------*/ int main(int argc, char** argv) { if (argc != 2) { PrintUsageAndExit(); } const char* input_filename = argv[1]; // open the input AP4_ByteStream* input = NULL; AP4_Result result = AP4_FileByteStream::Create(input_filename, AP4_FileByteStream::STREAM_MODE_READ, input); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: cannot open input file (%s)\n", input_filename); return 1; } // get the movie AP4_File* file = new AP4_File(*input, AP4_DefaultAtomFactory::Instance, true); AP4_Movie* movie = file->GetMovie(); AP4_Atom* atom = NULL; do { // process the next atom result = AP4_DefaultAtomFactory::Instance.CreateAtomFromStream(*input, atom); if (AP4_SUCCEEDED(result)) { printf("atom size=%lld\n", atom->GetSize()); if (atom->GetType() == AP4_ATOM_TYPE_MOOF) { AP4_ContainerAtom* moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); if (moof) { // remember where we are in the stream AP4_Position position = 0; input->Tell(position); // process the movie fragment ProcessMoof(movie, moof, input, position-atom->GetSize(), position+8); // go back to where we were before processing the fragment input->Seek(position); } } else { delete atom; } } } while (AP4_SUCCEEDED(result)); // cleanup delete file; input->Release(); return 0; }
/*---------------------------------------------------------------------- | AutoDetectAudioFragmentDuration +---------------------------------------------------------------------*/ static unsigned int AutoDetectAudioFragmentDuration(AP4_ByteStream& stream, TrackCursor* cursor) { // remember where we are in the stream AP4_Position where = 0; stream.Tell(where); AP4_LargeSize stream_size = 0; stream.GetSize(stream_size); AP4_LargeSize bytes_available = stream_size-where; AP4_UI64 fragment_count = 0; AP4_UI32 last_fragment_size = 0; AP4_Atom* atom = NULL; while (AP4_SUCCEEDED(AP4_DefaultAtomFactory::Instance.CreateAtomFromStream(stream, bytes_available, atom))) { if (atom && atom->GetType() == AP4_ATOM_TYPE_MOOF) { AP4_ContainerAtom* moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, moof->FindChild("traf/tfhd")); if (tfhd && tfhd->GetTrackId() == cursor->m_Track->GetId()) { ++fragment_count; AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, moof->FindChild("traf/trun")); if (trun) { last_fragment_size = trun->GetEntries().ItemCount(); } } } delete atom; atom = NULL; } // restore the stream to its original position stream.Seek(where); // decide if we can infer an fragment size if (fragment_count == 0 || cursor->m_Samples->GetSampleCount() == 0) { return 0; } // don't count the last fragment if we have more than one if (fragment_count > 1 && last_fragment_size) { --fragment_count; } if (fragment_count <= 1 || cursor->m_Samples->GetSampleCount() < last_fragment_size) { last_fragment_size = 0; } AP4_Sample sample; AP4_UI64 total_duration = 0; for (unsigned int i=0; i<cursor->m_Samples->GetSampleCount()-last_fragment_size; i++) { cursor->m_Samples->GetSample(i, sample); total_duration += sample.GetDuration(); } return (unsigned int)AP4_ConvertTime(total_duration/fragment_count, cursor->m_Track->GetMediaTimeScale(), 1000); }
/*---------------------------------------------------------------------- | AP4_FragmentSampleTable::AP4_FragmentSampleTable +---------------------------------------------------------------------*/ AP4_FragmentSampleTable::AP4_FragmentSampleTable(AP4_ContainerAtom* traf, AP4_TrexAtom* trex, AP4_Cardinal internal_track_id, AP4_ByteStream* sample_stream, AP4_Position moof_offset, AP4_Position mdat_payload_offset, AP4_UI64 dts_origin) :m_InternalTrackId(internal_track_id) { AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, traf->GetChild(AP4_ATOM_TYPE_TFHD)); if (tfhd == NULL) return; // count all the samples and reserve space for them unsigned int sample_count = 0; for (AP4_List<AP4_Atom>::Item* item = traf->GetChildren().FirstItem(); item; item = item->GetNext()) { AP4_Atom* atom = item->GetData(); if (atom->GetType() == AP4_ATOM_TYPE_TRUN) { AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, atom); if (trun) sample_count += trun->GetEntries().ItemCount(); } } m_Samples.EnsureCapacity(sample_count); // check if we have a timecode base AP4_TfdtAtom* tfdt = AP4_DYNAMIC_CAST(AP4_TfdtAtom, traf->GetChild(AP4_ATOM_TYPE_TFDT)); if (tfdt) { dts_origin = tfdt->GetBaseMediaDecodeTime(); } // process all the trun atoms for (AP4_List<AP4_Atom>::Item* item = traf->GetChildren().FirstItem(); item; item = item->GetNext()) { AP4_Atom* atom = item->GetData(); if (atom->GetType() == AP4_ATOM_TYPE_TRUN) { AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, atom); if (trun) { AP4_Result result = AddTrun(trun, tfhd, trex, sample_stream, moof_offset, mdat_payload_offset, dts_origin); if (AP4_FAILED(result)) return; } } } }
/*---------------------------------------------------------------------- | AP4_StsdAtom::AP4_StsdAtom +---------------------------------------------------------------------*/ AP4_StsdAtom::AP4_StsdAtom(AP4_SampleTable* sample_table) : AP4_ContainerAtom(AP4_ATOM_TYPE_STSD, 4+AP4_FULL_ATOM_HEADER_SIZE, true) { AP4_Cardinal sample_description_count = sample_table->GetSampleDescriptionCount(); m_SampleDescriptions.EnsureCapacity(sample_description_count); for (AP4_Ordinal i=0; i<sample_description_count; i++) { // clear the cache entry m_SampleDescriptions.Append(NULL); // create an entry for the description AP4_SampleDescription* sample_description = sample_table->GetSampleDescription(i); AP4_Atom* entry = sample_description->ToAtom(); m_Children.Add(entry); // update the size m_Size += entry->GetSize(); } }
/*---------------------------------------------------------------------- | AP4_File::AP4_File +---------------------------------------------------------------------*/ AP4_File::AP4_File(AP4_ByteStream& stream, AP4_AtomFactory& atom_factory) : m_Movie(NULL) { // get all atoms AP4_Atom* atom; while (AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(stream, atom))) { switch (atom->GetType()) { case AP4_ATOM_TYPE_MOOV: case AP4_ATOM_TYPE_3DVF: m_Movie = DNew AP4_Movie(dynamic_cast<AP4_MoovAtom*>(atom), stream); break; case AP4_ATOM_TYPE_FTYP: //m_Movie = DNew AP4_Movie(dynamic_cast<AP4_FtypAtom*>(atom), stream); m_FileType = dynamic_cast<AP4_FtypAtom*>(atom); default: m_OtherAtoms.Add(atom); } } }
/*---------------------------------------------------------------------- | Mp4ParserOutput_SetSampleDescription +---------------------------------------------------------------------*/ static BLT_Result Mp4ParserOutput_SetSampleDescription(Mp4ParserOutput* self, unsigned int indx) { // if we had a decrypter before, release it now delete self->sample_decrypter; self->sample_decrypter = NULL; // check that the audio track is of the right type AP4_SampleDescription* sample_desc = self->track->GetSampleDescription(indx); if (sample_desc == NULL) { ATX_LOG_FINE("no sample description for track"); return BLT_ERROR_INVALID_MEDIA_FORMAT; } // handle encrypted tracks BLT_Result result = Mp4ParserOutput_ProcessCryptoInfo(self, sample_desc); if (BLT_FAILED(result)) return result; // update the generic part of the stream info BLT_StreamInfo stream_info; stream_info.id = self->track->GetId(); stream_info.duration = self->track->GetDurationMs(); stream_info.mask = BLT_STREAM_INFO_MASK_ID | BLT_STREAM_INFO_MASK_DURATION; // deal with audio details, if this is an audio track AP4_AudioSampleDescription* audio_desc = dynamic_cast<AP4_AudioSampleDescription*>(sample_desc); if (audio_desc) { ATX_LOG_FINE("sample description is audio"); stream_info.type = BLT_STREAM_TYPE_AUDIO; stream_info.channel_count = audio_desc->GetChannelCount(); stream_info.sample_rate = audio_desc->GetSampleRate(); stream_info.mask |= BLT_STREAM_INFO_MASK_TYPE | BLT_STREAM_INFO_MASK_CHANNEL_COUNT | BLT_STREAM_INFO_MASK_SAMPLE_RATE; } else if (self == &self->parser->audio_output) { ATX_LOG_FINE("expected audio sample description, but did not get one"); return BLT_ERROR_INVALID_MEDIA_FORMAT; } AP4_VideoSampleDescription* video_desc = dynamic_cast<AP4_VideoSampleDescription*>(sample_desc); if (video_desc) { ATX_LOG_FINE("sample description is video"); stream_info.type = BLT_STREAM_TYPE_VIDEO; stream_info.width = video_desc->GetWidth(); stream_info.height = video_desc->GetHeight(); stream_info.mask |= BLT_STREAM_INFO_MASK_TYPE | BLT_STREAM_INFO_MASK_WIDTH | BLT_STREAM_INFO_MASK_HEIGHT; } else if (self == &self->parser->video_output) { ATX_LOG_FINE("expected video sample descriton, but did not get one"); return BLT_ERROR_INVALID_MEDIA_FORMAT; } AP4_MpegSampleDescription* mpeg_desc = NULL; if (sample_desc->GetType() == AP4_SampleDescription::TYPE_MPEG) { ATX_LOG_FINE("sample description is of type MPEG"); mpeg_desc = dynamic_cast<AP4_MpegSampleDescription*>(sample_desc); } if (mpeg_desc) { stream_info.data_type = mpeg_desc->GetObjectTypeString(mpeg_desc->GetObjectTypeId()); stream_info.average_bitrate = mpeg_desc->GetAvgBitrate(); stream_info.nominal_bitrate = mpeg_desc->GetAvgBitrate(); stream_info.mask |= BLT_STREAM_INFO_MASK_AVERAGE_BITRATE | BLT_STREAM_INFO_MASK_NOMINAL_BITRATE | BLT_STREAM_INFO_MASK_DATA_TYPE; } // setup the output media type AP4_DataBuffer decoder_info; BLT_MediaTypeId media_type_id = BLT_MEDIA_TYPE_ID_NONE; AP4_UI32 format_or_object_type_id = 0; if (mpeg_desc) { decoder_info.SetData(mpeg_desc->GetDecoderInfo().GetData(), mpeg_desc->GetDecoderInfo().GetDataSize()); media_type_id = self->mp4_es_type_id; format_or_object_type_id = mpeg_desc->GetObjectTypeId(); } else { // here we have to be format-specific for the decoder info stream_info.data_type = AP4_GetFormatName(sample_desc->GetFormat()); stream_info.mask |= BLT_STREAM_INFO_MASK_DATA_TYPE; format_or_object_type_id = sample_desc->GetFormat(); if (sample_desc->GetFormat() == AP4_SAMPLE_FORMAT_AVC1) { // look for an 'avcC' atom AP4_AvccAtom* avcc = static_cast<AP4_AvccAtom*>(sample_desc->GetDetails().GetChild(AP4_ATOM_TYPE_AVCC)); if (avcc) { // pass the avcc payload as the decoder info decoder_info.SetData(avcc->GetRawBytes().GetData(), avcc->GetRawBytes().GetDataSize()); } } else if (sample_desc->GetFormat() == AP4_SAMPLE_FORMAT_ALAC) { // look for an 'alac' atom (either top-level or inside a 'wave') AP4_Atom* alac = sample_desc->GetDetails().GetChild(AP4_SAMPLE_FORMAT_ALAC); if (alac == NULL) { AP4_ContainerAtom* wave = dynamic_cast<AP4_ContainerAtom*>(sample_desc->GetDetails().GetChild(AP4_ATOM_TYPE_WAVE)); if (wave) { alac = wave->GetChild(AP4_SAMPLE_FORMAT_ALAC); } } if (alac) { // pass the alac payload as the decoder info AP4_MemoryByteStream* mbs = new AP4_MemoryByteStream((AP4_Size)alac->GetSize()); alac->WriteFields(*mbs); decoder_info.SetData(mbs->GetData(), mbs->GetDataSize()); mbs->Release(); } } media_type_id = self->iso_base_es_type_id; } BLT_Mp4MediaType* media_type = NULL; unsigned int struct_size = decoder_info.GetDataSize()?decoder_info.GetDataSize()-1:0; if (audio_desc) { struct_size += sizeof(BLT_Mp4AudioMediaType); BLT_Mp4AudioMediaType* audio_type = (BLT_Mp4AudioMediaType*)ATX_AllocateZeroMemory(struct_size);; audio_type->base.stream_type = BLT_MP4_STREAM_TYPE_AUDIO; audio_type->channel_count = audio_desc->GetChannelCount(); audio_type->sample_rate = audio_desc->GetSampleRate(); audio_type->decoder_info_length = decoder_info.GetDataSize(); if (decoder_info.GetDataSize()) { ATX_CopyMemory(&audio_type->decoder_info[0], decoder_info.GetData(), decoder_info.GetDataSize()); } media_type = &audio_type->base; } else { struct_size += sizeof(BLT_Mp4VideoMediaType); BLT_Mp4VideoMediaType* video_type = (BLT_Mp4VideoMediaType*)ATX_AllocateZeroMemory(struct_size); video_type->base.stream_type = BLT_MP4_STREAM_TYPE_VIDEO; video_type->width = video_desc->GetWidth(); video_type->height = video_desc->GetHeight(); video_type->decoder_info_length = decoder_info.GetDataSize(); if (decoder_info.GetDataSize()) { ATX_CopyMemory(&video_type->decoder_info[0], decoder_info.GetData(), decoder_info.GetDataSize()); } media_type = &video_type->base; } media_type->base.id = media_type_id; media_type->base.extension_size = struct_size-sizeof(BLT_MediaType); media_type->format_or_object_type_id = format_or_object_type_id; self->media_type = &media_type->base; self->sample_description_index = indx; // final update to the stream info BLT_Stream_SetInfo(ATX_BASE(self->parser, BLT_BaseMediaNode).context, &stream_info); // enable the track in the linear reader if we have one if (self->parser->input.reader) { self->parser->input.reader->EnableTrack(self->track->GetId()); } return BLT_SUCCESS; }
/*---------------------------------------------------------------------- | AP4_Movie::ProcessMoof +---------------------------------------------------------------------*/ void AP4_Movie::ProcessMoof(AP4_ContainerAtom* moof, AP4_ByteStream& stream) { if (moof) { AP4_Offset offset = 0; stream.Tell(offset); AP4_Offset moof_offset = offset - moof->GetSize(); AP4_Offset mdat_payload_offset = offset + 8; AP4_MfhdAtom* mfhd = AP4_DYNAMIC_CAST(AP4_MfhdAtom, moof->GetChild(AP4_ATOM_TYPE_MFHD)); if (mfhd) { for (AP4_List<AP4_Atom>::Item* item = moof->GetChildren().FirstItem(); item; item = item->GetNext()) { AP4_Atom* atom = item->GetData(); if (atom->GetType() == AP4_ATOM_TYPE_TRAF) { AP4_ContainerAtom* traf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); if (traf) { AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, traf->GetChild(AP4_ATOM_TYPE_TFHD)); if (!tfhd) { continue; } AP4_Track* track = GetTrack(tfhd->GetTrackId()); if (!track) { continue; } AP4_TfdtAtom* tfdt = AP4_DYNAMIC_CAST(AP4_TfdtAtom, traf->GetChild(AP4_ATOM_TYPE_TFDT)); AP4_TrexAtom* trex = NULL; AP4_ContainerAtom* mvex = AP4_DYNAMIC_CAST(AP4_ContainerAtom, m_MoovAtom->GetChild(AP4_ATOM_TYPE_MVEX)); if (mvex) { for (AP4_List<AP4_Atom>::Item* child_item = mvex->GetChildren().FirstItem(); child_item; child_item = child_item->GetNext()) { AP4_Atom* child_atom = child_item->GetData(); if (child_atom->GetType() == AP4_ATOM_TYPE_TREX) { trex = AP4_DYNAMIC_CAST(AP4_TrexAtom, child_atom); if (trex && trex->GetTrackId() == tfhd->GetTrackId()) break; trex = NULL; } } } AP4_FragmentSampleTable* sampleTable = track->GetFragmentSampleTable(); AP4_Cardinal sample_count = 0; for (AP4_List<AP4_Atom>::Item* child_item = traf->GetChildren().FirstItem(); child_item; child_item = child_item->GetNext()) { AP4_Atom* child_atom = child_item->GetData(); if (child_atom->GetType() == AP4_ATOM_TYPE_TRUN) { AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, child_atom); if (trun) { sample_count += trun->GetEntries().ItemCount(); } } } if (!sample_count) { return; } if (sampleTable->GetSampleCount() == 0) { track->CreateFragmentFromStdSamples(); } if (AP4_FAILED(sampleTable->EnsureCapacity(sample_count + sampleTable->GetSampleCount()))) { return; } AP4_UI64 dts_origin = tfdt ? tfdt->GetBaseMediaDecodeTime() : 0; for (AP4_List<AP4_Atom>::Item* child_item = traf->GetChildren().FirstItem(); child_item; child_item = child_item->GetNext()) { AP4_Atom* child_atom = child_item->GetData(); if (child_atom->GetType() == AP4_ATOM_TYPE_TRUN) { AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, child_atom); if (trun) { sampleTable->AddTrun(trun, tfhd, trex, stream, dts_origin, moof_offset, mdat_payload_offset); } } } } } } } } }
/*---------------------------------------------------------------------- | main +---------------------------------------------------------------------*/ int main(int argc, char** argv) { if (argc < 4) { PrintUsageAndExit(); } // parse arguments const char* atom_path = NULL; const char* input_filename = NULL; const char* output_filename = NULL; bool payload_only = false; char* arg; while ((arg = *++argv)) { if (!strcmp(arg, "--payload-only")) { payload_only = true; } else if (atom_path == NULL) { atom_path = arg; } else if (input_filename == NULL) { input_filename = arg; } else if (output_filename == NULL) { output_filename = arg; } else { fprintf(stderr, "ERROR: invalid command line argument (%s)\n", arg); return 1; } } // check arguments if (atom_path == NULL) { fprintf(stderr, "ERROR: missing atom path\n"); return 1; } if (input_filename == NULL) { fprintf(stderr, "ERROR: missing input filename\n"); return 1; } if (output_filename == NULL) { fprintf(stderr, "ERROR: missing output filename\n"); return 1; } // create the input stream AP4_Result result; AP4_ByteStream* input = NULL; result = AP4_FileByteStream::Create(input_filename, AP4_FileByteStream::STREAM_MODE_READ, input); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: cannot open input file (%s)\n", input_filename); return 1; } // parse the atoms AP4_AtomParent top_level; AP4_Atom* atom; AP4_AtomFactory& atom_factory = AP4_DefaultAtomFactory::Instance; while (atom_factory.CreateAtomFromStream(*input, atom) == AP4_SUCCESS) { top_level.AddChild(atom); } // release the input input->Release(); // find the atom atom = top_level.FindChild(atom_path); if (atom == NULL) { fprintf(stderr, "ERROR: atom '%s' not found\n", atom_path); return 1; } // create the output stream AP4_ByteStream* output = NULL; result = AP4_FileByteStream::Create(output_filename, AP4_FileByteStream::STREAM_MODE_WRITE, output); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: cannot open output file (%s)\n", output_filename); return 1; } // write the atom if (payload_only) { atom->WriteFields(*output); } else { atom->Write(*output); } // cleanup output->Release(); return 0; }
/*---------------------------------------------------------------------- | AP4_MarlinIpmpParser:Parse +---------------------------------------------------------------------*/ AP4_Result AP4_MarlinIpmpParser::Parse(AP4_AtomParent& top_level, AP4_ByteStream& stream, AP4_List<SinfEntry>& sinf_entries, bool remove_od_data) { // check the file type AP4_FtypAtom* ftyp = AP4_DYNAMIC_CAST(AP4_FtypAtom, top_level.GetChild(AP4_ATOM_TYPE_FTYP)); if (ftyp == NULL || (ftyp->GetMajorBrand() != AP4_MARLIN_BRAND_MGSV && !ftyp->HasCompatibleBrand(AP4_MARLIN_BRAND_MGSV))) { return AP4_ERROR_INVALID_FORMAT; } // check the initial object descriptor and get the OD Track ID AP4_IodsAtom* iods = AP4_DYNAMIC_CAST(AP4_IodsAtom, top_level.FindChild("moov/iods")); AP4_UI32 od_track_id = 0; if (iods == NULL) return AP4_ERROR_INVALID_FORMAT; const AP4_ObjectDescriptor* od = iods->GetObjectDescriptor(); if (od == NULL) return AP4_ERROR_INVALID_FORMAT; AP4_EsIdIncDescriptor* es_id_inc = AP4_DYNAMIC_CAST(AP4_EsIdIncDescriptor, od->FindSubDescriptor(AP4_DESCRIPTOR_TAG_ES_ID_INC)); if (es_id_inc == NULL) return AP4_ERROR_INVALID_FORMAT; od_track_id = es_id_inc->GetTrackId(); // find the track pointed to by the descriptor AP4_MoovAtom* moov = AP4_DYNAMIC_CAST(AP4_MoovAtom, top_level.GetChild(AP4_ATOM_TYPE_MOOV)); if (moov == NULL) return AP4_ERROR_INVALID_FORMAT; AP4_TrakAtom* od_trak = NULL; AP4_List<AP4_TrakAtom>::Item* trak_item = moov->GetTrakAtoms().FirstItem(); while (trak_item) { AP4_TrakAtom* trak = trak_item->GetData(); if (trak) { if (trak->GetId() == od_track_id) { od_trak = trak; } else { sinf_entries.Add(new SinfEntry(trak->GetId(), NULL)); } } trak_item = trak_item->GetNext(); } // check that we have found the OD track if (od_trak == NULL) return AP4_ERROR_INVALID_FORMAT; // look for the 'mpod' trak references AP4_TrefTypeAtom* track_references; track_references = AP4_DYNAMIC_CAST(AP4_TrefTypeAtom, od_trak->FindChild("tref/mpod")); if (track_references == NULL) return AP4_ERROR_INVALID_FORMAT; // create an AP4_Track object from the trak atom and check that it has samples AP4_Track* od_track = new AP4_Track(*od_trak, stream, 0); if (od_track->GetSampleCount() < 1) { delete od_track; return AP4_ERROR_INVALID_FORMAT; } // get the first sample (in this version, we only look at a single OD command) AP4_Sample od_sample; AP4_Result result = od_track->GetSample(0, od_sample); if (AP4_FAILED(result)) { delete od_track; return AP4_ERROR_INVALID_FORMAT; } // adapt the sample data into a byte stream for parsing AP4_DataBuffer sample_data; od_sample.ReadData(sample_data); AP4_MemoryByteStream* sample_stream = new AP4_MemoryByteStream(sample_data); // look for one ObjectDescriptorUpdate command and // one IPMP_DescriptorUpdate command AP4_DescriptorUpdateCommand* od_update = NULL; AP4_DescriptorUpdateCommand* ipmp_update = NULL; do { AP4_Command* command = NULL; result = AP4_CommandFactory::CreateCommandFromStream(*sample_stream, command); if (AP4_SUCCEEDED(result)) { // found a command in the sample, check the type switch (command->GetTag()) { case AP4_COMMAND_TAG_OBJECT_DESCRIPTOR_UPDATE: if (od_update == NULL) { od_update = AP4_DYNAMIC_CAST(AP4_DescriptorUpdateCommand, command); } break; case AP4_COMMAND_TAG_IPMP_DESCRIPTOR_UPDATE: if (ipmp_update == NULL) { ipmp_update = AP4_DYNAMIC_CAST(AP4_DescriptorUpdateCommand, command); } break; default: break; } } } while (AP4_SUCCEEDED(result)); sample_stream->Release(); sample_stream = NULL; // check that we have what we need if (od_update == NULL || ipmp_update == NULL) { delete od_track; return AP4_ERROR_INVALID_FORMAT; } // process all the object descriptors in the od update for (AP4_List<AP4_Descriptor>::Item* od_item = od_update->GetDescriptors().FirstItem(); od_item; od_item = od_item->GetNext()) { od = AP4_DYNAMIC_CAST(AP4_ObjectDescriptor, od_item->GetData()); if (od == NULL) continue; // find which track this od references AP4_EsIdRefDescriptor* es_id_ref; es_id_ref = AP4_DYNAMIC_CAST(AP4_EsIdRefDescriptor, od->FindSubDescriptor(AP4_DESCRIPTOR_TAG_ES_ID_REF)); if (es_id_ref == NULL || es_id_ref->GetRefIndex() > track_references->GetTrackIds().ItemCount() || es_id_ref->GetRefIndex() == 0) { continue; } AP4_UI32 track_id = track_references->GetTrackIds()[es_id_ref->GetRefIndex()-1]; SinfEntry* sinf_entry = NULL; for (AP4_List<SinfEntry>::Item* sinf_entry_item = sinf_entries.FirstItem(); sinf_entry_item; sinf_entry_item = sinf_entry_item->GetNext()) { sinf_entry = sinf_entry_item->GetData(); if (sinf_entry->m_TrackId == track_id) { break; // match } else { sinf_entry = NULL; // no match } } if (sinf_entry == NULL) continue; // no matching entry if (sinf_entry->m_Sinf != NULL) continue; // entry already populated // see what ipmp descriptor this od points to AP4_IpmpDescriptorPointer* ipmpd_pointer; ipmpd_pointer = AP4_DYNAMIC_CAST(AP4_IpmpDescriptorPointer, od->FindSubDescriptor(AP4_DESCRIPTOR_TAG_IPMP_DESCRIPTOR_POINTER)); if (ipmpd_pointer == NULL) continue; // no pointer // find the ipmp descriptor referenced by the pointer AP4_IpmpDescriptor* ipmpd = NULL; for (AP4_List<AP4_Descriptor>::Item* ipmpd_item = ipmp_update->GetDescriptors().FirstItem(); ipmpd_item; ipmpd_item = ipmpd_item->GetNext()) { // check that this descriptor is of the right type ipmpd = AP4_DYNAMIC_CAST(AP4_IpmpDescriptor, ipmpd_item->GetData()); if (ipmpd == NULL || ipmpd->GetIpmpsType() != AP4_MARLIN_IPMPS_TYPE_MGSV) continue; // check the descriptor id if (ipmpd->GetDescriptorId() == ipmpd_pointer->GetDescriptorId()) { break; // match } else { ipmpd = NULL; // no match } } if (ipmpd == NULL) continue; // no matching entry // parse the ipmp data into one or more 'sinf' atoms, and keep the one with the // right type AP4_MemoryByteStream* data = new AP4_MemoryByteStream(ipmpd->GetData().GetData(), ipmpd->GetData().GetDataSize()); AP4_LargeSize bytes_available = ipmpd->GetData().GetDataSize(); do { AP4_Atom* atom = NULL; // setup the factory with a context so we can instantiate an 'schm' // atom with a slightly different format than the standard 'schm' AP4_AtomFactory* factory = &AP4_MarlinIpmpAtomFactory::Instance; factory->PushContext(AP4_ATOM_TYPE('m','r','l','n')); // parse the next atom in the stream result = factory->CreateAtomFromStream(*data, bytes_available, atom); factory->PopContext(); if (AP4_FAILED(result) || atom == NULL) break; // check that what we have parsed is indeed an 'sinf' of the right type if (atom->GetType() == AP4_ATOM_TYPE_SINF) { AP4_ContainerAtom* sinf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); AP4_SchmAtom* schm = AP4_DYNAMIC_CAST(AP4_SchmAtom, sinf->FindChild("schm")); if (schm->GetSchemeType() == AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACBC && schm->GetSchemeVersion() == 0x0100) { // store the sinf in the entry for that track sinf_entry->m_Sinf = sinf; break; } } delete atom; } while (AP4_SUCCEEDED(result)); data->Release(); } // remove the iods atom and the OD track if required if (remove_od_data) { od_trak->Detach(); delete od_trak; iods->Detach(); delete iods; } // cleanup delete od_track; return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | AP4_MarlinIpmpDecryptingProcessor:CreateTrackHandler +---------------------------------------------------------------------*/ AP4_Processor::TrackHandler* AP4_MarlinIpmpDecryptingProcessor::CreateTrackHandler(AP4_TrakAtom* trak) { // look for this track in the list of entries AP4_MarlinIpmpParser::SinfEntry* sinf_entry = NULL; for (AP4_List<AP4_MarlinIpmpParser::SinfEntry>::Item* sinf_entry_item = m_SinfEntries.FirstItem(); sinf_entry_item; sinf_entry_item = sinf_entry_item->GetNext()) { sinf_entry = sinf_entry_item->GetData(); if (sinf_entry->m_TrackId == trak->GetId()) { break; // match } else { sinf_entry = NULL; // no match } } if (sinf_entry == NULL) return NULL; // no matching entry AP4_ContainerAtom* sinf = sinf_entry->m_Sinf; // check the scheme bool use_group_key; AP4_SchmAtom* schm = AP4_DYNAMIC_CAST(AP4_SchmAtom, sinf->GetChild(AP4_ATOM_TYPE_SCHM)); if (schm == NULL) return NULL; // no schm if (schm->GetSchemeType() == AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACBC && schm->GetSchemeVersion() == 0x0100) { use_group_key = false; } else if (schm->GetSchemeType() == AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACGK && schm->GetSchemeVersion() == 0x0100) { use_group_key = true; } else { // unsupported scheme return NULL; } // find the key const AP4_DataBuffer* key = NULL; AP4_DataBuffer unwrapped_key; if (use_group_key) { const AP4_DataBuffer* group_key = m_KeyMap.GetKey(0); if (group_key == NULL) return NULL; // no group key AP4_ContainerAtom* schi = AP4_DYNAMIC_CAST(AP4_ContainerAtom, sinf->GetChild(AP4_ATOM_TYPE_SCHI)); if (schi == NULL) return NULL; // no schi AP4_Atom* gkey = schi->GetChild(AP4_ATOM_TYPE_GKEY); if (gkey == NULL) return NULL; // no gkey AP4_MemoryByteStream* gkey_data = new AP4_MemoryByteStream(); gkey->WriteFields(*gkey_data); AP4_AesKeyUnwrap(group_key->GetData(), gkey_data->GetData(), gkey_data->GetDataSize(), unwrapped_key); key = &unwrapped_key; gkey_data->Release(); } else { key = m_KeyMap.GetKey(sinf_entry->m_TrackId); } if (key == NULL) return NULL; // create the decrypter AP4_MarlinIpmpTrackDecrypter* decrypter = NULL; AP4_Result result = AP4_MarlinIpmpTrackDecrypter::Create(*m_BlockCipherFactory, key->GetData(), key->GetDataSize(), decrypter); if (AP4_FAILED(result)) return NULL; return decrypter; }
/*---------------------------------------------------------------------- | AP4_Processor::MuxStream +---------------------------------------------------------------------*/ AP4_Result AP4_Processor::MuxStream( AP4_Array<AP4_ByteStream *> &input, AP4_ByteStream& output, AP4_UI08 partitions, AP4_AtomFactory& atom_factory) { AP4_Result result; AP4_UI64 stream_offset = 0; if (partitions & 1) { // read all atoms. // keep all atoms except [mdat] // keep a ref to [moov] // put [moof] atoms in a separate list AP4_AtomParent top_level; AP4_Array<AP4_MoovAtom*> moov; AP4_Size track_count(0); for(AP4_Size streamid(0); streamid < input.ItemCount(); ++streamid) { for (AP4_Atom* atom = NULL; AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(*input[streamid], atom)); input[streamid]->Tell(stream_offset)) { if (atom->GetType() == AP4_ATOM_TYPE_MFRA) { delete atom; continue; } else if (atom->GetType() == AP4_ATOM_TYPE_SIDX) { delete atom; continue; } else if (atom->GetType() == AP4_ATOM_TYPE_SSIX) { delete atom; continue; } if (streamid == 0) top_level.AddChild(atom); else if (atom->GetType() != AP4_ATOM_TYPE_MOOV) delete atom; if (atom->GetType() == AP4_ATOM_TYPE_MOOV) { moov.Append(AP4_DYNAMIC_CAST(AP4_MoovAtom,atom)); break; } } if (moov.ItemCount() == streamid) return AP4_ERROR_INVALID_FORMAT; while (AP4_SUCCEEDED(moov[streamid]->DeleteChild(AP4_ATOM_TYPE_PSSH, 0))); // Remove tracks we cannot handle for (AP4_List<AP4_TrakAtom>::Item *item(moov[streamid]->GetTrakAtoms().FirstItem()); item;) if (!item->GetData()->FindChild("mdia/minf/stbl")) moov[streamid]->GetTrakAtoms().Remove(item); else item = item->GetNext(); track_count += moov[streamid]->GetTrakAtoms().ItemCount(); } // initialize the processor if (AP4_FAILED(result = Initialize(top_level, *input[0]))) return result; // process the tracks if we have a moov atom m_TrackData.SetItemCount(track_count); m_StreamData.SetItemCount(input.ItemCount()); //NormalizeTREX(mvex, 0, m_TrackCounts[0], m_TrackCounts[1]); AP4_Cardinal internal_index(0); AP4_ContainerAtom *mvex_base(0); AP4_List<AP4_TrakAtom>::Item *item = NULL; for (AP4_Size streamid(0); streamid < input.ItemCount(); ++streamid) { m_StreamData[streamid].trackStart = internal_index; m_StreamData[streamid].stream = input[streamid]; if (streamid) moov[0]->AddTrakAtoms(moov[streamid]->GetTrakAtoms(), item); else item = moov[streamid]->GetTrakAtoms().FirstItem(); for (; item; item = item->GetNext()) { PERTRACK &track_data(m_TrackData[internal_index]); track_data.original_id = item->GetData()->GetId(); item->GetData()->SetId(track_data.new_id = internal_index + 1); if (AP4_MdhdAtom* mdhd = AP4_DYNAMIC_CAST(AP4_MdhdAtom, item->GetData()->FindChild("mdia/mdhd"))) track_data.timescale = mdhd->GetTimeScale(); else track_data.timescale = 1; AP4_ContainerAtom *mvex = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moov[streamid]->GetChild(AP4_ATOM_TYPE_MVEX, 0)); if (!mvex) return AP4_ERROR_INVALID_FORMAT; if (!item->GetData()->GetDuration()) { AP4_MehdAtom *mehd(AP4_DYNAMIC_CAST(AP4_MehdAtom, mvex->GetChild(AP4_ATOM_TYPE_MEHD, 0))); item->GetData()->SetDuration(mehd? mehd->GetDuration():0); } AP4_TrexAtom *trex(NULL); unsigned int index(0); for (; !trex && (trex = AP4_DYNAMIC_CAST(AP4_TrexAtom, mvex->GetChild(AP4_ATOM_TYPE_TREX, index++)));) if(trex->GetTrackId() != track_data.original_id) trex = NULL; if (!trex) return AP4_ERROR_INVALID_FORMAT; if (mvex_base) { trex = AP4_DYNAMIC_CAST(AP4_TrexAtom, trex->Clone()); mvex_base->AddChild(trex); } else mvex_base = mvex; trex->SetTrackId(track_data.new_id); track_data.track_handler = CreateTrackHandler(item->GetData(), trex); track_data.track_handler->ProcessTrack(); track_data.streamId = streamid; ++m_StreamData[streamid].trackCount; ++internal_index; } } // We don't need the other moovs anymore..... moov.SetItemCount(1); AP4_MvhdAtom *mvhd(AP4_DYNAMIC_CAST(AP4_MvhdAtom, moov[0]->GetChild(AP4_ATOM_TYPE_MVHD, 0))); if (!mvhd->GetDuration()) { AP4_MehdAtom *mehd(AP4_DYNAMIC_CAST(AP4_MehdAtom, mvex_base->GetChild(AP4_ATOM_TYPE_MEHD, 0))); mvhd->SetDuration(mehd ? mehd->GetDuration() : 0); } // finalize the processor Finalize(top_level); // calculate the size of all atoms combined AP4_UI64 atoms_size = 0; top_level.GetChildren().Apply(AP4_AtomSizeAdder(atoms_size)); // write all atoms top_level.GetChildren().Apply(AP4_AtomListWriter(output)); m_MoovAtom = moov[0]; m_MoovAtom->Detach(); } if (partitions & 2) { // process the fragments, if any result = AP4_SUCCESS; AP4_Array<AP4_UI64> moof_positions, mdat_positions; moof_positions.SetItemCount(input.ItemCount()); mdat_positions.SetItemCount(input.ItemCount()); for (;;) { AP4_ContainerAtom *moof = NULL; AP4_UI32 track_index(0); #if 0 for (AP4_Cardinal streamid(0); streamid < input.ItemCount(); ++streamid) { AP4_Atom* atom = NULL; if (AP4_SUCCEEDED(input[streamid]->Tell(stream_offset)) && AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(*input[streamid], atom))) { if (atom->GetType() != AP4_ATOM_TYPE_MOOF) return AP4_ERROR_INVALID_FORMAT; moof_positions[streamid] = stream_offset; mdat_positions[streamid] = stream_offset + atom->GetSize() + +AP4_ATOM_HEADER_SIZE; if (moof) { int index(0); for (; AP4_Atom* child = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom)->GetChild(AP4_ATOM_TYPE_TRAF, index++);) moof->AddChild(child->Clone()); delete atom; } else moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); NormalizeTRAF(AP4_DYNAMIC_CAST(AP4_ContainerAtom, moof), m_StreamData[streamid].trackStart, m_StreamData[streamid].trackStart + m_StreamData[streamid].trackCount, track_index); } else delete atom; } #else double mindts(9999999999.0); AP4_Cardinal nextStream(~0); for (AP4_Cardinal track(0); track < m_TrackData.ItemCount(); ++track) if ((double)m_TrackData[track].dts / m_TrackData[track].timescale < mindts) { mindts = (double)m_TrackData[track].dts / m_TrackData[track].timescale; nextStream = m_TrackData[track].streamId; } AP4_Atom* atom = NULL; if (AP4_SUCCEEDED(result = input[nextStream]->Tell(stream_offset)) && AP4_SUCCEEDED(result = atom_factory.CreateAtomFromStream(*input[nextStream], atom))) { if (atom->GetType() != AP4_ATOM_TYPE_MOOF) return AP4_ERROR_INVALID_FORMAT; } else if (atom) return result; moof_positions[nextStream] = stream_offset; mdat_positions[nextStream] = stream_offset + atom->GetSize() + +AP4_ATOM_HEADER_SIZE; moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); NormalizeTRAF(AP4_DYNAMIC_CAST(AP4_ContainerAtom, moof), m_StreamData[nextStream].trackStart, m_StreamData[nextStream].trackStart + m_StreamData[nextStream].trackCount, track_index); #endif if (!moof) break; if (AP4_FAILED(result = ProcessFragment(moof, NULL, 0, output, moof_positions, mdat_positions))) return result; delete moof; moof = NULL; } // cleanup m_TrackData.Clear(); m_StreamData.Clear(); } return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | AP4_Processor::Process +---------------------------------------------------------------------*/ AP4_Result AP4_Processor::Process(AP4_ByteStream& input, AP4_ByteStream& output, AP4_ByteStream* fragments, ProgressListener* listener, AP4_AtomFactory& atom_factory) { // read all atoms. // keep all atoms except [mdat] // keep a ref to [moov] // put [moof] atoms in a separate list AP4_AtomParent top_level; AP4_MoovAtom* moov = NULL; AP4_ContainerAtom* mfra = NULL; AP4_SidxAtom* sidx = NULL; AP4_List<AP4_AtomLocator> frags; AP4_UI64 stream_offset = 0; bool in_fragments = false; unsigned int sidx_count = 0; for (AP4_Atom* atom = NULL; AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(input, atom)); input.Tell(stream_offset)) { if (atom->GetType() == AP4_ATOM_TYPE_MDAT) { delete atom; continue; } else if (atom->GetType() == AP4_ATOM_TYPE_MOOV) { moov = AP4_DYNAMIC_CAST(AP4_MoovAtom, atom); if (fragments) break; } else if (atom->GetType() == AP4_ATOM_TYPE_MFRA) { mfra = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); continue; } else if (atom->GetType() == AP4_ATOM_TYPE_SIDX) { // don't keep the index, it is likely to be invalidated, we will recompute it later ++sidx_count; if (sidx == NULL) { sidx = AP4_DYNAMIC_CAST(AP4_SidxAtom, atom); } else { delete atom; continue; } } else if (atom->GetType() == AP4_ATOM_TYPE_SSIX) { // don't keep the index, it is likely to be invalidated delete atom; continue; } else if (!fragments && (in_fragments || atom->GetType() == AP4_ATOM_TYPE_MOOF)) { in_fragments = true; frags.Add(new AP4_AtomLocator(atom, stream_offset)); break; } top_level.AddChild(atom); } // check that we have at most one sidx (we can't deal with multi-sidx streams here if (sidx_count > 1) { top_level.RemoveChild(sidx); delete sidx; sidx = NULL; } // if we have a fragments stream, get the fragment locators from there if (fragments) { stream_offset = 0; for (AP4_Atom* atom = NULL; AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(*fragments, atom)); fragments->Tell(stream_offset)) { if (atom->GetType() == AP4_ATOM_TYPE_MDAT) { delete atom; continue; } frags.Add(new AP4_AtomLocator(atom, stream_offset)); } } // initialize the processor AP4_Result result = Initialize(top_level, input); if (AP4_FAILED(result)) return result; // process the tracks if we have a moov atom AP4_Array<AP4_SampleLocator> locators; AP4_Cardinal track_count = 0; AP4_List<AP4_TrakAtom>* trak_atoms = NULL; AP4_LargeSize mdat_payload_size = 0; AP4_SampleCursor* cursors = NULL; if (moov) { // build an array of track sample locators trak_atoms = &moov->GetTrakAtoms(); track_count = trak_atoms->ItemCount(); cursors = new AP4_SampleCursor[track_count]; m_TrackData.SetItemCount(track_count); m_StreamData.SetItemCount(1); m_StreamData[0].stream = &input; unsigned int index = 0; for (AP4_List<AP4_TrakAtom>::Item* item = trak_atoms->FirstItem(); item; item=item->GetNext()) { AP4_TrakAtom* trak = item->GetData(); // find the stsd atom AP4_ContainerAtom* stbl = AP4_DYNAMIC_CAST(AP4_ContainerAtom, trak->FindChild("mdia/minf/stbl")); if (stbl == NULL) continue; // see if there's an external data source for this track AP4_ByteStream* trak_data_stream = &input; for (AP4_List<ExternalTrackData>::Item* ditem = m_ExternalTrackData.FirstItem(); ditem; ditem=ditem->GetNext()) { ExternalTrackData* tdata = ditem->GetData(); if (tdata->m_TrackId == trak->GetId()) { trak_data_stream = tdata->m_MediaData; break; } } AP4_ContainerAtom *mvex = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moov->GetChild(AP4_ATOM_TYPE_MVEX)); AP4_TrexAtom* trex = NULL; if (mvex) { for (AP4_List<AP4_Atom>::Item* item = mvex->GetChildren().FirstItem(); item; item = item->GetNext()) { AP4_Atom* atom = item->GetData(); if (atom->GetType() == AP4_ATOM_TYPE_TREX) { trex = AP4_DYNAMIC_CAST(AP4_TrexAtom, atom); if (trex && trex->GetTrackId() == trak->GetId()) break; trex = NULL; } } } // create the track handler m_TrackData[index].track_handler = CreateTrackHandler(trak, trex); m_TrackData[index].new_id = trak->GetId(); cursors[index].m_Locator.m_TrakIndex = index; cursors[index].m_Locator.m_SampleTable = new AP4_AtomSampleTable(stbl, *trak_data_stream); cursors[index].m_Locator.m_SampleIndex = 0; cursors[index].m_Locator.m_ChunkIndex = 0; if (cursors[index].m_Locator.m_SampleTable->GetSampleCount()) { cursors[index].m_Locator.m_SampleTable->GetSample(0, cursors[index].m_Locator.m_Sample); } else { cursors[index].m_EndReached = true; } index++; } // figure out the layout of the chunks for (;;) { // see which is the next sample to write AP4_UI64 min_offset = (AP4_UI64)(-1); int cursor = -1; for (unsigned int i=0; i<track_count; i++) { if (!cursors[i].m_EndReached && cursors[i].m_Locator.m_Sample.GetOffset() <= min_offset) { min_offset = cursors[i].m_Locator.m_Sample.GetOffset(); cursor = i; } } // stop if all cursors are exhausted if (cursor == -1) break; // append this locator to the layout list AP4_SampleLocator& locator = cursors[cursor].m_Locator; locators.Append(locator); // move the cursor to the next sample locator.m_SampleIndex++; if (locator.m_SampleIndex == locator.m_SampleTable->GetSampleCount()) { // mark this track as completed cursors[cursor].m_EndReached = true; } else { // get the next sample info locator.m_SampleTable->GetSample(locator.m_SampleIndex, locator.m_Sample); AP4_Ordinal skip, sdesc; locator.m_SampleTable->GetChunkForSample(locator.m_SampleIndex, locator.m_ChunkIndex, skip, sdesc); } } // update the stbl atoms and compute the mdat size int current_track = -1; int current_chunk = -1; AP4_Position current_chunk_offset = 0; AP4_Size current_chunk_size = 0; for (AP4_Ordinal i=0; i<locators.ItemCount(); i++) { AP4_SampleLocator& locator = locators[i]; if ((int)locator.m_TrakIndex != current_track || (int)locator.m_ChunkIndex != current_chunk) { // start a new chunk for this track current_chunk_offset += current_chunk_size; current_chunk_size = 0; current_track = locator.m_TrakIndex; current_chunk = locator.m_ChunkIndex; locator.m_SampleTable->SetChunkOffset(locator.m_ChunkIndex, current_chunk_offset); } AP4_Size sample_size; TrackHandler* handler = m_TrackData[locator.m_TrakIndex].track_handler; if (handler) { sample_size = handler->GetProcessedSampleSize(locator.m_Sample); locator.m_SampleTable->SetSampleSize(locator.m_SampleIndex, sample_size); } else { sample_size = locator.m_Sample.GetSize(); } current_chunk_size += sample_size; mdat_payload_size += sample_size; } // process the tracks (ex: sample descriptions processing) for (AP4_Ordinal i=0; i<track_count; i++) { TrackHandler* handler = m_TrackData[i].track_handler; if (handler) handler->ProcessTrack(); } } // finalize the processor Finalize(top_level); if (!fragments) { // calculate the size of all atoms combined AP4_UI64 atoms_size = 0; top_level.GetChildren().Apply(AP4_AtomSizeAdder(atoms_size)); // see if we need a 64-bit or 32-bit mdat AP4_Size mdat_header_size = AP4_ATOM_HEADER_SIZE; if (mdat_payload_size+mdat_header_size > 0xFFFFFFFF) { // we need a 64-bit size mdat_header_size += 8; } // adjust the chunk offsets for (AP4_Ordinal i=0; i<track_count; i++) { AP4_TrakAtom* trak; trak_atoms->Get(i, trak); trak->AdjustChunkOffsets(atoms_size+mdat_header_size); } // write all atoms top_level.GetChildren().Apply(AP4_AtomListWriter(output)); // write mdat header if (mdat_payload_size) { if (mdat_header_size == AP4_ATOM_HEADER_SIZE) { // 32-bit size output.WriteUI32((AP4_UI32)(mdat_header_size+mdat_payload_size)); output.WriteUI32(AP4_ATOM_TYPE_MDAT); } else { // 64-bit size output.WriteUI32(1); output.WriteUI32(AP4_ATOM_TYPE_MDAT); output.WriteUI64(mdat_header_size+mdat_payload_size); } } } // write the samples if (moov) { if (!fragments) { #if defined(AP4_DEBUG) AP4_Position before; output.Tell(before); #endif AP4_Sample sample; AP4_DataBuffer data_in; AP4_DataBuffer data_out; for (unsigned int i=0; i<locators.ItemCount(); i++) { AP4_SampleLocator& locator = locators[i]; locator.m_Sample.ReadData(data_in); TrackHandler* handler = m_TrackData[locator.m_TrakIndex].track_handler; if (handler) { result = handler->ProcessSample(data_in, data_out); if (AP4_FAILED(result)) return result; output.Write(data_out.GetData(), data_out.GetDataSize()); } else { output.Write(data_in.GetData(), data_in.GetDataSize()); } // notify the progress listener if (listener) { listener->OnProgress(i+1, locators.ItemCount()); } } #if defined(AP4_DEBUG) AP4_Position after; output.Tell(after); AP4_ASSERT(after-before == mdat_payload_size); #endif } else m_StreamData[0].stream = fragments; // find the position of the sidx atom AP4_Position sidx_position = 0; if (sidx) { for (AP4_List<AP4_Atom>::Item* item = top_level.GetChildren().FirstItem(); item; item = item->GetNext()) { AP4_Atom* atom = item->GetData(); if (atom->GetType() == AP4_ATOM_TYPE_SIDX) { break; } sidx_position += atom->GetSize(); } } // process the fragments, if any AP4_Array<AP4_Position> moof_offsets, mdat_offsets; moof_offsets.SetItemCount(1); mdat_offsets.SetItemCount(1); while (frags.ItemCount() > 0) { for (AP4_List<AP4_AtomLocator>::Item *locator(frags.FirstItem()); locator; locator = locator->GetNext()) { AP4_ContainerAtom *moof(AP4_DYNAMIC_CAST(AP4_ContainerAtom, locator->GetData()->m_Atom)); moof_offsets[0] = locator->GetData()->m_Offset; mdat_offsets[0] = moof_offsets[0] + moof->GetSize() + AP4_ATOM_HEADER_SIZE; result = ProcessFragment(moof, sidx, sidx_position, output, moof_offsets, mdat_offsets); if (AP4_FAILED(result)) return result; } frags.DeleteReferences(); AP4_Atom* atom = NULL; input.Tell(stream_offset); if (AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(input, atom))) { if (atom->GetType() == AP4_ATOM_TYPE_MOOF) frags.Add(new AP4_AtomLocator(atom, stream_offset)); else delete atom; } } // update the mfra if we have one if (mfra) { for (AP4_List<AP4_Atom>::Item* mfra_item = mfra->GetChildren().FirstItem(); mfra_item; mfra_item = mfra_item->GetNext()) { if (mfra_item->GetData()->GetType() != AP4_ATOM_TYPE_TFRA) continue; AP4_TfraAtom* tfra = AP4_DYNAMIC_CAST(AP4_TfraAtom, mfra_item->GetData()); if (tfra == NULL) continue; AP4_Array<AP4_TfraAtom::Entry>& entries = tfra->GetEntries(); AP4_Cardinal entry_count = entries.ItemCount(); for (unsigned int i = 0; i<entry_count; i++) { entries[i].m_MoofOffset = FindFragmentMapEntry(entries[i].m_MoofOffset); } } } // update and re-write the sidx if we have one if (sidx && sidx_position) { AP4_Position where = 0; output.Tell(where); output.Seek(sidx_position); result = sidx->Write(output); if (AP4_FAILED(result)) return result; output.Seek(where); } if (!fragments) { // write the mfra atom at the end if we have one if (mfra) { mfra->Write(output); } } // cleanup for (AP4_Ordinal i=0; i<track_count; i++) delete cursors[i].m_Locator.m_SampleTable; m_TrackData.Clear(); delete[] cursors; } // cleanup frags.DeleteReferences(); delete mfra; return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | main +---------------------------------------------------------------------*/ int main(int argc, char** argv) { if (argc < 2) { PrintUsageAndExit(); } // init the variables AP4_UI32 track_id = 0; AP4_ByteStream* input = NULL; AP4_ByteStream* track_data = NULL; // parse the command line argv++; char* arg; while ((arg = *argv++)) { if (!strcmp(arg, "-track")) { if (argv[0] && argv[1] && argv[2]) { track_id = atoi(argv[0]); if (track_id == 0) PrintUsageAndExit(); track_data = new AP4_FileByteStream(argv[1], AP4_FileByteStream::STREAM_MODE_WRITE); argv += 2; } else { PrintUsageAndExit(); } } else { try { input = new AP4_FileByteStream(arg, AP4_FileByteStream::STREAM_MODE_READ); } catch(AP4_Exception e) { AP4_Debug("ERROR: cannot open input (%d)\n", e.m_Error); return 1; } } } // open the output AP4_ByteStream* output = new AP4_FileByteStream("-stdout", AP4_FileByteStream::STREAM_MODE_WRITE); // create an inspector AP4_PrintInspector inspector(*output); // inspect the atoms one by one AP4_Atom* atom; //for (int i=0; i<1000; i++) { AP4_AtomFactory& atom_factory = AP4_AtomFactory::DefaultFactory; //MyTypeHandler my_type_handler; //atom_factory.AddTypeHandler(&my_type_handler); while (atom_factory.CreateAtomFromStream(*input, atom) == AP4_SUCCESS) { atom->Inspect(inspector); delete atom; } //input->Seek(0); //} // inspect the track data if needed if ((track_id != 0) && (track_data != NULL)) { //DumpTrackData(file, track_id, track_data); } if (input) input->Release(); if (output) output->Release(); if (track_data) track_data->Release(); return 0; }
/*---------------------------------------------------------------------- | main +---------------------------------------------------------------------*/ int main(int argc, char** argv) { if (argc < 2) { PrintUsageAndExit(); } // default options Options.verbose = false; Options.input = NULL; Options.init_segment_name = AP4_SPLIT_DEFAULT_INIT_SEGMENT_NAME; Options.media_segment_name = AP4_SPLIT_DEFAULT_MEDIA_SEGMENT_NAME; Options.pattern_params = AP4_SPLIT_DEFAULT_PATTERN_PARAMS; Options.start_number = 1; Options.track_id = 0; Options.audio_only = false; Options.video_only = false; Options.init_only = false; Options.track_filter = 0; // parse command line AP4_Result result; char** args = argv+1; while (const char* arg = *args++) { if (!strcmp(arg, "--verbose")) { Options.verbose = true; } else if (!strcmp(arg, "--init-segment")) { if (*args == NULL) { fprintf(stderr, "ERROR: missing argument after --init-segment option\n"); return 1; } Options.init_segment_name = *args++; } else if (!strcmp(arg, "--media-segment")) { if (*args == NULL) { fprintf(stderr, "ERROR: missing argument after --media-segment option\n"); return 1; } Options.media_segment_name = *args++; } else if (!strcmp(arg, "--pattern-parameters")) { if (*args == NULL) { fprintf(stderr, "ERROR: missing argument after --pattern-params option\n"); return 1; } Options.pattern_params = *args++; } else if (!strcmp(arg, "--track-id")) { Options.track_id = strtoul(*args++, NULL, 10); } else if (!strcmp(arg, "--start-number")) { Options.start_number = strtoul(*args++, NULL, 10); } else if (!strcmp(arg, "--init-only")) { Options.init_only = true; } else if (!strcmp(arg, "--audio")) { Options.audio_only = true; } else if (!strcmp(arg, "--video")) { Options.video_only = true; } else if (Options.input == NULL) { Options.input = arg; } else { fprintf(stderr, "ERROR: unexpected argument\n"); return 1; } } // check args if (Options.input == NULL) { fprintf(stderr, "ERROR: missing input file name\n"); return 1; } if ((Options.audio_only && (Options.video_only || Options.track_id)) || (Options.video_only && (Options.audio_only || Options.track_id)) || (Options.track_id && (Options.audio_only || Options.video_only))) { fprintf(stderr, "ERROR: --audio, --video and --track-id options are mutualy exclusive\n"); return 1; } if (strlen(Options.pattern_params) < 1) { fprintf(stderr, "ERROR: --pattern-params argument is too short\n"); return 1; } if (strlen(Options.pattern_params) > 2) { fprintf(stderr, "ERROR: --pattern-params argument is too long\n"); return 1; } const char* cursor = Options.pattern_params; while (*cursor) { if (*cursor != 'I' && *cursor != 'N') { fprintf(stderr, "ERROR: invalid pattern parameter '%c'\n", *cursor); return 1; } ++cursor; } // create the input stream AP4_ByteStream* input = NULL; result = AP4_FileByteStream::Create(Options.input, AP4_FileByteStream::STREAM_MODE_READ, input); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: cannot open input (%d)\n", result); return 1; } // get the movie AP4_File* file = new AP4_File(*input, AP4_DefaultAtomFactory::Instance, true); AP4_Movie* movie = file->GetMovie(); if (movie == NULL) { fprintf(stderr, "no movie found in file\n"); return 1; } // filter tracks if required if (Options.audio_only) { AP4_Track* track = movie->GetTrack(AP4_Track::TYPE_AUDIO); if (track == NULL) { fprintf(stderr, "--audio option specified, but no audio track found\n"); return 1; } Options.track_filter = track->GetId(); } else if (Options.video_only) { AP4_Track* track = movie->GetTrack(AP4_Track::TYPE_VIDEO); if (track == NULL) { fprintf(stderr, "--video option specified, but no video track found\n"); return 1; } Options.track_filter = track->GetId(); } else if (Options.track_id) { AP4_Track* track = movie->GetTrack(Options.track_id); if (track == NULL) { fprintf(stderr, "--track-id option specified, but no such track found\n"); return 1; } Options.track_filter = track->GetId(); } // save the init segment AP4_ByteStream* output = NULL; result = AP4_FileByteStream::Create(Options.init_segment_name, AP4_FileByteStream::STREAM_MODE_WRITE, output); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: cannot open output file (%d)\n", result); return 1; } AP4_FtypAtom* ftyp = file->GetFileType(); if (ftyp) { result = ftyp->Write(*output); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: cannot write ftyp segment (%d)\n", result); return 1; } } if (Options.track_filter) { AP4_MoovAtom* moov = movie->GetMoovAtom(); // only keep the 'trak' atom that we need AP4_List<AP4_Atom>::Item* child = moov->GetChildren().FirstItem(); while (child) { AP4_Atom* atom = child->GetData(); child = child->GetNext(); if (atom->GetType() == AP4_ATOM_TYPE_TRAK) { AP4_TrakAtom* trak = (AP4_TrakAtom*)atom; AP4_TkhdAtom* tkhd = (AP4_TkhdAtom*)trak->GetChild(AP4_ATOM_TYPE_TKHD); if (tkhd && tkhd->GetTrackId() != Options.track_filter) { atom->Detach(); delete atom; } } } // only keep the 'trex' atom that we need AP4_ContainerAtom* mvex = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moov->GetChild(AP4_ATOM_TYPE_MVEX)); if (mvex) { child = mvex->GetChildren().FirstItem(); while (child) { AP4_Atom* atom = child->GetData(); child = child->GetNext(); if (atom->GetType() == AP4_ATOM_TYPE_TREX) { AP4_TrexAtom* trex = AP4_DYNAMIC_CAST(AP4_TrexAtom, atom); if (trex && trex->GetTrackId() != Options.track_filter) { atom->Detach(); delete atom; } } } } } result = movie->GetMoovAtom()->Write(*output); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: cannot write init segment (%d)\n", result); return 1; } AP4_Atom* atom = NULL; unsigned int track_id = 0; for (;!Options.init_only;) { // process the next atom result = AP4_DefaultAtomFactory::Instance.CreateAtomFromStream(*input, atom); if (AP4_FAILED(result)) break; if (atom->GetType() == AP4_ATOM_TYPE_MOOF) { AP4_ContainerAtom* moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); unsigned int traf_count = 0; AP4_ContainerAtom* traf = NULL; do { traf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moof->GetChild(AP4_ATOM_TYPE_TRAF, traf_count)); if (traf == NULL) break; AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, traf->GetChild(AP4_ATOM_TYPE_TFHD)); if (tfhd == NULL) { fprintf(stderr, "ERROR: invalid media format\n"); return 1; } track_id = tfhd->GetTrackId(); traf_count++; } while (traf); // check if this fragment has more than one traf if (traf_count > 1) { if (Options.audio_only) { fprintf(stderr, "ERROR: --audio option incompatible with multi-track fragments"); return 1; } if (Options.video_only) { fprintf(stderr, "ERROR: --video option incompatible with multi-track fragments"); return 1; } track_id = 0; } // open a new file for this fragment if (output) { output->Release(); output = NULL; } char segment_name[4096]; if (Options.track_filter == 0 || Options.track_filter == track_id) { AP4_UI64 p[2] = {0,0}; unsigned int params_len = strlen(Options.pattern_params); for (unsigned int i=0; i<params_len; i++) { if (Options.pattern_params[i] == 'I') { p[i] = track_id; } else if (Options.pattern_params[i] == 'N') { p[i] = NextFragmentIndex(track_id)+Options.start_number; } } switch (params_len) { case 1: sprintf(segment_name, Options.media_segment_name, p[0]); break; case 2: sprintf(segment_name, Options.media_segment_name, p[0], p[1]); break; default: segment_name[0] = 0; break; } result = AP4_FileByteStream::Create(segment_name, AP4_FileByteStream::STREAM_MODE_WRITE, output); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: cannot open output file (%d)\n", result); return 1; } } } // write the atom if (output && atom->GetType() != AP4_ATOM_TYPE_MFRA) { atom->Write(*output); } delete atom; } // cleanup delete file; if (input) input->Release(); if (output) output->Release(); return 0; }
/*---------------------------------------------------------------------- | AP4_MoovAtom::AP4_MoovAtom +---------------------------------------------------------------------*/ AP4_MoovAtom::AP4_MoovAtom(AP4_Size size, AP4_ByteStream& stream, AP4_AtomFactory& atom_factory) : AP4_ContainerAtom(AP4_ATOM_TYPE_MOOV, size, false, stream, atom_factory), m_TimeScale(0) { if(AP4_ContainerAtom* cmov = dynamic_cast<AP4_ContainerAtom*>(GetChild(AP4_ATOM_TYPE_CMOV))) { AP4_DcomAtom* dcom = dynamic_cast<AP4_DcomAtom*>(cmov->GetChild(AP4_ATOM_TYPE_DCOM)); AP4_CmvdAtom* cmvd = dynamic_cast<AP4_CmvdAtom*>(cmov->GetChild(AP4_ATOM_TYPE_CMVD)); if(dcom && dcom->GetCompressorSubType() == AP4_ATOM_TYPE('z','l','i','b') && cmvd) { const AP4_DataBuffer& data = cmvd->GetDataBuffer(); z_stream d_stream; d_stream.zalloc = (alloc_func)0; d_stream.zfree = (free_func)0; d_stream.opaque = (voidpf)0; int res; if(Z_OK == (res = inflateInit(&d_stream))) { d_stream.next_in = (Bytef*)data.GetData(); d_stream.avail_in = data.GetDataSize(); unsigned char* dst = NULL; int n = 0; do { dst = (unsigned char*)realloc(dst, ++n*1000); d_stream.next_out = &dst[(n-1)*1000]; d_stream.avail_out = 1000; if(Z_OK != (res = inflate(&d_stream, Z_NO_FLUSH)) && Z_STREAM_END != res) { free(dst); dst = NULL; break; } } while(0 == d_stream.avail_out && 0 != d_stream.avail_in && Z_STREAM_END != res); inflateEnd(&d_stream); if(dst) { AP4_ByteStream* s = new AP4_MemoryByteStream(dst, d_stream.total_out); ReadChildren(atom_factory, *s, d_stream.total_out); s->Release(); free(dst); } if(AP4_MoovAtom* moov = dynamic_cast<AP4_MoovAtom*>(GetChild(AP4_ATOM_TYPE_MOOV))) { AP4_List<AP4_Atom> Children; for(AP4_List<AP4_Atom>::Item* item = moov->GetChildren().FirstItem(); item; item = item->GetNext()) { Children.Add(item->GetData()); } for(AP4_List<AP4_Atom>::Item* item = Children.FirstItem(); item; item = item->GetNext()) { AP4_Atom* atom = item->GetData(); atom->Detach(); atom->SetParent(this); m_Children.Add(atom); } moov->Detach(); delete moov; } } } } // collect all trak atoms m_Children.Apply(AP4_TrakAtomCollector(&m_TrakAtoms)); }
/*---------------------------------------------------------------------- | AP4_Processor::Process +---------------------------------------------------------------------*/ AP4_Result AP4_Processor::Process(AP4_ByteStream& input, AP4_ByteStream& output, ProgressListener* listener, AP4_AtomFactory& atom_factory) { // read all atoms AP4_AtomParent top_level; AP4_Atom* atom; while (AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(input, atom))) { top_level.AddChild(atom); } // remove the [mdat] atom, keep a ref to [moov] AP4_MoovAtom* moov = NULL; AP4_List<AP4_Atom>::Item* atom_item = top_level.GetChildren().FirstItem(); while (atom_item) { atom = atom_item->GetData(); AP4_List<AP4_Atom>::Item* next = atom_item->GetNext(); if (atom->GetType() == AP4_ATOM_TYPE_MDAT) { atom->Detach(); delete atom; } else if (atom->GetType() == AP4_ATOM_TYPE_MOOV) { moov = (AP4_MoovAtom*)atom; } atom_item = next; } // initialize the processor AP4_Result result = Initialize(top_level, input); if (AP4_FAILED(result)) return result; // process the tracks if we have a moov atom AP4_Array<AP4_SampleLocator> locators; AP4_Cardinal track_count = 0; AP4_List<AP4_TrakAtom>* trak_atoms = NULL; AP4_LargeSize mdat_payload_size = 0; TrackHandler** handlers = NULL; AP4_SampleCursor* cursors = NULL; if (moov) { // build an array of track sample locators trak_atoms = &moov->GetTrakAtoms(); track_count = trak_atoms->ItemCount(); cursors = new AP4_SampleCursor[track_count]; handlers = new TrackHandler*[track_count]; for (AP4_Ordinal i=0; i<track_count; i++) { handlers[i] = NULL; } unsigned int index = 0; for (AP4_List<AP4_TrakAtom>::Item* item = trak_atoms->FirstItem(); item; item=item->GetNext()) { AP4_TrakAtom* trak = item->GetData(); // find the stsd atom AP4_ContainerAtom* stbl = AP4_DYNAMIC_CAST(AP4_ContainerAtom, trak->FindChild("mdia/minf/stbl")); if (stbl == NULL) continue; // see if there's an external data source for this track AP4_ByteStream* trak_data_stream = &input; for (AP4_List<ExternalTrackData>::Item* ditem = m_ExternalTrackData.FirstItem(); ditem; ditem=ditem->GetNext()) { ExternalTrackData* tdata = ditem->GetData(); if (tdata->m_TrackId == trak->GetId()) { trak_data_stream = tdata->m_MediaData; break; } } // create the track handler handlers[index] = CreateTrackHandler(trak); cursors[index].m_Locator.m_TrakIndex = index; cursors[index].m_Locator.m_SampleTable = new AP4_AtomSampleTable(stbl, *trak_data_stream); cursors[index].m_Locator.m_SampleIndex = 0; cursors[index].m_Locator.m_ChunkIndex = 0; cursors[index].m_Locator.m_SampleTable->GetSample(0, cursors[index].m_Locator.m_Sample); index++; } // figure out the layout of the chunks for (;;) { // see which is the next sample to write AP4_UI64 min_offset = (AP4_UI64)(-1); int cursor = -1; for (unsigned int i=0; i<track_count; i++) { if (!cursors[i].m_EndReached && cursors[i].m_Locator.m_Sample.GetOffset() <= min_offset) { min_offset = cursors[i].m_Locator.m_Sample.GetOffset(); cursor = i; } } // stop if all cursors are exhausted if (cursor == -1) break; // append this locator to the layout list AP4_SampleLocator& locator = cursors[cursor].m_Locator; locators.Append(locator); // move the cursor to the next sample locator.m_SampleIndex++; if (locator.m_SampleIndex == locator.m_SampleTable->GetSampleCount()) { // mark this track as completed cursors[cursor].m_EndReached = true; } else { // get the next sample info locator.m_SampleTable->GetSample(locator.m_SampleIndex, locator.m_Sample); AP4_Ordinal skip, sdesc; locator.m_SampleTable->GetChunkForSample(locator.m_SampleIndex, locator.m_ChunkIndex, skip, sdesc); } } // update the stbl atoms and compute the mdat size int current_track = -1; int current_chunk = -1; AP4_Position current_chunk_offset = 0; AP4_Size current_chunk_size = 0; for (AP4_Ordinal i=0; i<locators.ItemCount(); i++) { AP4_SampleLocator& locator = locators[i]; if ((int)locator.m_TrakIndex != current_track || (int)locator.m_ChunkIndex != current_chunk) { // start a new chunk for this track current_chunk_offset += current_chunk_size; current_chunk_size = 0; current_track = locator.m_TrakIndex; current_chunk = locator.m_ChunkIndex; locator.m_SampleTable->SetChunkOffset(locator.m_ChunkIndex, current_chunk_offset); } AP4_Size sample_size; TrackHandler* handler = handlers[locator.m_TrakIndex]; if (handler) { sample_size = handler->GetProcessedSampleSize(locator.m_Sample); locator.m_SampleTable->SetSampleSize(locator.m_SampleIndex, sample_size); } else { sample_size = locator.m_Sample.GetSize(); } current_chunk_size += sample_size; mdat_payload_size += sample_size; } // process the tracks (ex: sample descriptions processing) for (AP4_Ordinal i=0; i<track_count; i++) { TrackHandler* handler = handlers[i]; if (handler) handler->ProcessTrack(); } } // initialize the processor Finalize(top_level); // calculate the size of all atoms combined AP4_UI64 atoms_size = 0; top_level.GetChildren().Apply(AP4_AtomSizeAdder(atoms_size)); // see if we need a 64-bit or 32-bit mdat AP4_Size mdat_header_size = AP4_ATOM_HEADER_SIZE; if (mdat_payload_size+mdat_header_size > 0xFFFFFFFF) { // we need a 64-bit size mdat_header_size += 8; } // adjust the chunk offsets for (AP4_Ordinal i=0; i<track_count; i++) { AP4_TrakAtom* trak; trak_atoms->Get(i, trak); trak->AdjustChunkOffsets(atoms_size+mdat_header_size); } // write all atoms top_level.GetChildren().Apply(AP4_AtomListWriter(output)); // write mdat header if (mdat_payload_size) { if (mdat_header_size == AP4_ATOM_HEADER_SIZE) { // 32-bit size output.WriteUI32((AP4_UI32)(mdat_header_size+mdat_payload_size)); output.WriteUI32(AP4_ATOM_TYPE_MDAT); } else { // 64-bit size output.WriteUI32(1); output.WriteUI32(AP4_ATOM_TYPE_MDAT); output.WriteUI64(mdat_header_size+mdat_payload_size); } } #if defined(AP4_DEBUG) AP4_Position before; output.Tell(before); #endif // write the samples if (moov) { AP4_Sample sample; AP4_DataBuffer data_in; AP4_DataBuffer data_out; for (unsigned int i=0; i<locators.ItemCount(); i++) { AP4_SampleLocator& locator = locators[i]; locator.m_Sample.ReadData(data_in); TrackHandler* handler = handlers[locator.m_TrakIndex]; if (handler) { result = handler->ProcessSample(data_in, data_out); if (AP4_FAILED(result)) return result; output.Write(data_out.GetData(), data_out.GetDataSize()); } else { output.Write(data_in.GetData(), data_in.GetDataSize()); } // notify the progress listener if (listener) { listener->OnProgress(i+1, locators.ItemCount()); } } // cleanup for (AP4_Ordinal i=0; i<track_count; i++) { delete cursors[i].m_Locator.m_SampleTable; delete handlers[i]; } delete[] cursors; delete[] handlers; } #if defined(AP4_DEBUG) AP4_Position after; output.Tell(after); AP4_ASSERT(after-before == mdat_payload_size); #endif return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | AP4_Processor::Process +---------------------------------------------------------------------*/ AP4_Result AP4_Processor::Process(AP4_ByteStream& input, AP4_ByteStream& output, AP4_AtomFactory& atom_factory) { // read all atoms AP4_AtomParent top_level; AP4_Atom* atom; while (AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(input, atom))) { top_level.AddChild(atom); } // remove the [mdat] and [free] atoms, keep a ref to [moov] AP4_MoovAtom* moov = NULL; AP4_List<AP4_Atom>::Item* atom_item = top_level.GetChildren().FirstItem(); while (atom_item) { atom = atom_item->GetData(); AP4_List<AP4_Atom>::Item* next = atom_item->GetNext(); if (//atom->GetType() == AP4_ATOM_TYPE_FREE || atom->GetType() == AP4_ATOM_TYPE_MDAT) { atom->Detach(); delete atom; } else if (atom->GetType() == AP4_ATOM_TYPE_MOOV) { moov = (AP4_MoovAtom*)atom; } atom_item = next; } // check that we have a moov atom if (moov == NULL) return AP4_FAILURE; // initialize the processor AP4_Result result = Initialize(top_level); if (AP4_FAILED(result)) return result; // build an array of track sample cursors AP4_List<AP4_TrakAtom>& trak_atoms = moov->GetTrakAtoms(); AP4_Cardinal track_count = trak_atoms.ItemCount(); AP4_SampleCursor* cursors = new AP4_SampleCursor[track_count]; TrackHandler** handlers = new TrackHandler*[track_count]; AP4_List<AP4_TrakAtom>::Item* item = trak_atoms.FirstItem(); unsigned int index = 0; while (item) { // create the track handler // find the stsd atom AP4_ContainerAtom* stbl = dynamic_cast<AP4_ContainerAtom*>( item->GetData()->FindChild("mdia/minf/stbl")); if (stbl == NULL) continue; handlers[index] = CreateTrackHandler(item->GetData()); cursors[index].m_Locator.m_TrakIndex = index; cursors[index].m_Locator.m_SampleTable = new AP4_AtomSampleTable(stbl, input); cursors[index].m_Locator.m_SampleIndex = 0; cursors[index].m_Locator.m_SampleTable->GetSample(0, cursors[index].m_Locator.m_Sample); cursors[index].m_Locator.m_Chunk = 1; index++; item = item->GetNext(); } // figure out the layout of the chunks AP4_Array<AP4_SampleLocator> locators; for (;;) { // see which is the next sample to write unsigned int min_offset = 0xFFFFFFFF; int cursor = -1; for (unsigned int i=0; i<track_count; i++) { if (cursors[i].m_Locator.m_SampleTable && cursors[i].m_Locator.m_Sample.GetOffset() <= min_offset) { min_offset = cursors[i].m_Locator.m_Sample.GetOffset(); cursor = i; } } // stop if all cursors are exhausted if (cursor == -1) break; // append this locator to the layout list AP4_SampleLocator& locator = cursors[cursor].m_Locator; locators.Append(locator); //AP4_Debug("NEXT: track %d, sample %d:%d: offset=%d, size=%d\n", // locator.m_TrakIndex, // locator.m_Chunk, // locator.m_SampleIndex, // locator.m_Sample.GetOffset(), // locator.m_Sample.GetSize()); // move the cursor to the next sample locator.m_SampleIndex++; if (locator.m_SampleIndex == locator.m_SampleTable->GetSampleCount()) { // mark this track as completed locator.m_SampleTable = NULL; } else { // get the next sample info locator.m_SampleTable->GetSample(locator.m_SampleIndex, locator.m_Sample); AP4_Ordinal skip, sdesc; locator.m_SampleTable->GetChunkForSample(locator.m_SampleIndex+1, // the internal API is 1-based locator.m_Chunk, skip, sdesc); } } // update the stbl atoms and compute the mdat size AP4_Size mdat_size = 0; int current_track = -1; int current_chunk = -1; AP4_Offset current_chunk_offset = 0; AP4_Size current_chunk_size = 0; for (AP4_Ordinal i=0; i<locators.ItemCount(); i++) { AP4_SampleLocator& locator = locators[i]; if ((int)locator.m_TrakIndex != current_track || (int)locator.m_Chunk != current_chunk) { // start a new chunk for this track current_chunk_offset += current_chunk_size; current_chunk_size = 0; current_track = locator.m_TrakIndex; current_chunk = locator.m_Chunk; locator.m_SampleTable->SetChunkOffset(locator.m_Chunk, current_chunk_offset); } AP4_Size sample_size; TrackHandler* handler = handlers[locator.m_TrakIndex]; if (handler) { sample_size = handler->GetProcessedSampleSize(locator.m_Sample); locator.m_SampleTable->SetSampleSize(locator.m_SampleIndex+1, sample_size); } else { sample_size = locator.m_Sample.GetSize(); } current_chunk_size += sample_size; mdat_size += sample_size; } // process the tracks (ex: sample descriptions processing) for (AP4_Ordinal i=0; i<track_count; i++) { TrackHandler* handler = handlers[i]; if (handler) handler->ProcessTrack(); } // initialize the processor Finalize(top_level); // calculate the size of all atoms combined AP4_Size atoms_size = 0; top_level.GetChildren().Apply(AP4_AtomSizeAdder(atoms_size)); // adjust the chunk offsets for (AP4_Ordinal i=0; i<track_count; i++) { AP4_TrakAtom* trak; trak_atoms.Get(i, trak); trak->AdjustChunkOffsets(atoms_size+AP4_ATOM_HEADER_SIZE); } // write all atoms top_level.GetChildren().Apply(AP4_AtomListWriter(output)); // write mdat header output.WriteUI32(mdat_size+AP4_ATOM_HEADER_SIZE); output.WriteUI32(AP4_ATOM_TYPE_MDAT); #if defined(AP4_DEBUG) AP4_Offset before; output.Tell(before); #endif // write the samples AP4_Sample sample; AP4_DataBuffer data_in; AP4_DataBuffer data_out; for (unsigned int i=0; i<locators.ItemCount(); i++) { AP4_SampleLocator& locator = locators[i]; locator.m_Sample.ReadData(data_in); TrackHandler* handler = handlers[locator.m_TrakIndex]; if (handler) { handler->ProcessSample(data_in, data_out); output.Write(data_out.GetData(), data_out.GetDataSize()); } else { output.Write(data_in.GetData(), data_in.GetDataSize()); } } #if defined(AP4_DEBUG) AP4_Offset after; output.Tell(after); AP4_ASSERT(after-before == mdat_size); #endif // cleanup delete[] cursors; for (unsigned int i=0; i<track_count; i++) { delete handlers[i]; } delete[] handlers; return AP4_SUCCESS; }