/*---------------------------------------------------------------------- | 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; }
/*---------------------------------------------------------------------- | AP4_LinearReader::SeekTo +---------------------------------------------------------------------*/ AP4_Result AP4_LinearReader::SeekTo(AP4_UI32 time_ms, AP4_UI32* actual_time_ms) { if (actual_time_ms) *actual_time_ms = time_ms; // default // we only support fragmented sources for now if (!m_HasFragments) return AP4_ERROR_NOT_SUPPORTED; // look for a fragment index if (m_Mfra == NULL) { if (m_FragmentStream) { // get the size of the stream (needed) AP4_LargeSize stream_size = 0; m_FragmentStream->GetSize(stream_size); if (stream_size > 12) { // remember where we are AP4_Position here; m_FragmentStream->Tell(here); // read the last 12 bytes unsigned char mfro[12]; AP4_Result result = m_FragmentStream->Seek(stream_size-12); if (AP4_SUCCEEDED(result)) { result = m_FragmentStream->Read(mfro, 12); } if (AP4_SUCCEEDED(result) && mfro[0] == 'm' && mfro[1] == 'f' && mfro[2] == 'r' && mfro[3] == 'o') { AP4_UI32 mfra_size = AP4_BytesToUInt32BE(&mfro[8]); if ((AP4_LargeSize)mfra_size < stream_size) { result = m_FragmentStream->Seek(stream_size-mfra_size); if (AP4_SUCCEEDED(result)) { AP4_Atom* mfra = NULL; AP4_LargeSize available = mfra_size; AP4_DefaultAtomFactory::Instance.CreateAtomFromStream(*m_FragmentStream, available, mfra); m_Mfra = AP4_DYNAMIC_CAST(AP4_ContainerAtom, mfra); } } } if (AP4_SUCCEEDED(result)) { result = m_FragmentStream->Seek(here); } } } } // return now if we have not found an index if (m_Mfra == NULL) { return AP4_ERROR_NOT_SUPPORTED; } // look for the earliest fragment referenced by an entry with the largest timestamp that's // before or equal to the requested time int best_entry = -1; for (unsigned t=0; t<m_Trackers.ItemCount(); t++) { // find the tfra index for this track AP4_TfraAtom* tfra = NULL; for (AP4_List<AP4_Atom>::Item* item = m_Mfra->GetChildren().FirstItem(); item; item = item->GetNext()) { if (item->GetData()->GetType() == AP4_ATOM_TYPE_TFRA) { AP4_TfraAtom* tfra_ = (AP4_TfraAtom*)item->GetData(); if (tfra_->GetTrackId() == m_Trackers[t]->m_Track->GetId()) { tfra = tfra_; break; } } } if (tfra == NULL) { return AP4_ERROR_NOT_SUPPORTED; } AP4_Array<AP4_TfraAtom::Entry>& entries = tfra->GetEntries(); AP4_UI64 media_time = AP4_ConvertTime(time_ms, 1000, m_Trackers[t]->m_Track->GetMediaTimeScale()); int entry = -1; for (int i=0; i<(int)entries.ItemCount(); i++) { if (entries[i].m_Time > media_time) break; entry = i; } if (entry >= 0) { if (best_entry == -1) { best_entry = entry; } else if (entries[entry].m_MoofOffset < entries[best_entry].m_MoofOffset) { best_entry = entry; } // update our position if (best_entry >= 0) { if (actual_time_ms) { // report the actual time we found (in milliseconds) *actual_time_ms = (AP4_UI32)AP4_ConvertTime(entries[best_entry].m_Time, m_Trackers[t]->m_Track->GetMediaTimeScale(), 1000); } m_NextFragmentPosition = entries[best_entry].m_MoofOffset; } } } // check that we found something if (best_entry == -1) { return AP4_FAILURE; } // flush any queued samples FlushQueues(); // reset tracker states for (unsigned int i=0; i<m_Trackers.ItemCount(); i++) { delete m_Trackers[i]->m_SampleTable; delete m_Trackers[i]->m_NextSample; m_Trackers[i]->m_SampleTable = NULL; m_Trackers[i]->m_NextSample = NULL; m_Trackers[i]->m_NextSampleIndex = 0; m_Trackers[i]->m_Eos = false; } return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | AP4_Processor::ProcessFragments +---------------------------------------------------------------------*/ AP4_Result AP4_Processor::ProcessFragments(AP4_MoovAtom* moov, AP4_List<AP4_MoofLocator>& moofs, AP4_ContainerAtom* mfra, AP4_ByteStream& input, AP4_ByteStream& output) { // FIXME: this only works for non-changing moofs for (AP4_List<AP4_MoofLocator>::Item* item = moofs.FirstItem(); item; item = item->GetNext()) { AP4_MoofLocator* locator = item->GetData(); AP4_ContainerAtom* moof = locator->m_Moof; AP4_UI64 moof_offset = locator->m_Offset; AP4_UI64 mdat_payload_offset = moof_offset+moof->GetSize()+8; AP4_MovieFragment* fragment = new AP4_MovieFragment(moof); AP4_Sample sample; AP4_DataBuffer sample_data_in; AP4_DataBuffer sample_data_out; AP4_Result result; // process all the traf atoms AP4_Array<AP4_Processor::FragmentHandler*> handlers; for (;AP4_Atom* atom = moof->GetChild(AP4_ATOM_TYPE_TRAF, handlers.ItemCount());) { AP4_ContainerAtom* traf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); AP4_Processor::FragmentHandler* handler = CreateFragmentHandler(traf); if (handler) result = handler->ProcessFragment(); handlers.Append(handler); } // write the moof AP4_UI64 moof_out_start = 0; output.Tell(moof_out_start); bool moof_has_changed = false; moof->Write(output); // process all track runs for (unsigned int i=0; i<handlers.ItemCount(); i++) { AP4_FragmentSampleTable* sample_table = NULL; AP4_Processor::FragmentHandler* handler = handlers[i]; // get the track ID AP4_ContainerAtom* traf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moof->GetChild(AP4_ATOM_TYPE_TRAF, i)); AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, traf->GetChild(AP4_ATOM_TYPE_TFHD, i)); // create a sample table object so we can read the sample data result = fragment->CreateSampleTable(moov, tfhd->GetTrackId(), &input, moof_offset, mdat_payload_offset, sample_table); if (AP4_FAILED(result)) return result; // compute the mdat size AP4_UI64 mdat_size = 0; for (unsigned int j=0; j<sample_table->GetSampleCount(); j++) { result = sample_table->GetSample(j, sample); if (AP4_FAILED(result)) return result; mdat_size += sample.GetSize(); } // write an mdat header if (mdat_size > 0xFFFFFFFF-8) { // we don't support large mdat fragments return AP4_ERROR_OUT_OF_RANGE; } if (mdat_size) { output.WriteUI32((AP4_UI32)(8+mdat_size)); output.WriteUI32(AP4_ATOM_TYPE_MDAT); } #if defined(AP4_DEBUG) AP4_Position before; output.Tell(before); #endif // write the mdat for (unsigned int j=0; j<sample_table->GetSampleCount(); j++) { result = sample_table->GetSample(j, sample); if (AP4_FAILED(result)) return result; sample.ReadData(sample_data_in); // process the sample data if (handler) { result = handler->ProcessSample(sample_data_in, sample_data_out); if (AP4_FAILED(result)) return result; // write the sample data result = output.Write(sample_data_out.GetData(), sample_data_out.GetDataSize()); if (AP4_FAILED(result)) return result; // give the handler a chance to update the atoms result = handler->FinishFragment(); if (AP4_SUCCEEDED(result)) moof_has_changed = true; } else { // write the sample data (unmodified) result = output.Write(sample_data_in.GetData(), sample_data_in.GetDataSize()); if (AP4_FAILED(result)) return result; } } #if defined(AP4_DEBUG) AP4_Position after; output.Tell(after); AP4_ASSERT(after-before == mdat_size); #endif delete sample_table; } // update the moof if needed AP4_UI64 mdat_out_end = 0; output.Tell(mdat_out_end); if (moof_has_changed) { output.Seek(moof_out_start); moof->Write(output); output.Seek(mdat_out_end); } // 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++) { if (entries[i].m_MoofOffset == locator->m_Offset) { entries[i].m_MoofOffset = moof_out_start; } } } } delete fragment; } return AP4_SUCCESS; }