/*---------------------------------------------------------------------- | AP4_Expandable::AP4_Expandable +---------------------------------------------------------------------*/ AP4_Expandable::AP4_Expandable(AP4_UI32 class_id, ClassIdSize class_id_size, AP4_Size header_size, AP4_Size payload_size) : m_ClassId(class_id), m_ClassIdSize(class_id_size), m_HeaderSize(header_size), m_PayloadSize(payload_size) { AP4_ASSERT(header_size >= 1+1); AP4_ASSERT(header_size <= 1+4); }
/*---------------------------------------------------------------------- | AP4_AtomSampleTable::AP4_AtomSampleTable +---------------------------------------------------------------------*/ AP4_AtomSampleTable::AP4_AtomSampleTable(AP4_ContainerAtom* stbl, AP4_ByteStream& sample_stream) : m_SampleStream(sample_stream) { m_StscAtom = dynamic_cast<AP4_StscAtom*>(stbl->GetChild(AP4_ATOM_TYPE_STSC)); m_StcoAtom = dynamic_cast<AP4_StcoAtom*>(stbl->GetChild(AP4_ATOM_TYPE_STCO)); m_Co64Atom = dynamic_cast<AP4_Co64Atom*>(stbl->GetChild(AP4_ATOM_TYPE_CO64)); m_StszAtom = dynamic_cast<AP4_StszAtom*>(stbl->GetChild(AP4_ATOM_TYPE_STSZ)); m_CttsAtom = dynamic_cast<AP4_CttsAtom*>(stbl->GetChild(AP4_ATOM_TYPE_CTTS)); m_SttsAtom = dynamic_cast<AP4_SttsAtom*>(stbl->GetChild(AP4_ATOM_TYPE_STTS)); m_StssAtom = dynamic_cast<AP4_StssAtom*>(stbl->GetChild(AP4_ATOM_TYPE_STSS)); m_StsdAtom = dynamic_cast<AP4_StsdAtom*>(stbl->GetChild(AP4_ATOM_TYPE_STSD)); // keep a reference to the sample stream m_SampleStream.AddReference(); if(m_StsdAtom && m_StszAtom && m_StscAtom && m_SttsAtom && m_StszAtom->m_SampleSize == 1) { // fix mov files for(AP4_List<AP4_Atom>::Item* item = m_StsdAtom->GetChildren().FirstItem(); item; item = item->GetNext()) { AP4_Atom* atom = item->GetData(); if(AP4_AudioSampleEntry* ase = dynamic_cast<AP4_AudioSampleEntry*>(atom)) { AP4_UI32 SamplesPerPacket = ase->GetSamplesPerPacket(); AP4_UI32 BytesPerFrame = ase->GetBytesPerFrame(); if(SamplesPerPacket > 0 && BytesPerFrame > 0) { for(int i = 0, j = m_StscAtom->m_Entries.ItemCount(); i < j; i++) { AP4_StscTableEntry& e = m_StscAtom->m_Entries[i]; AP4_ASSERT(e.m_SamplesPerChunk % SamplesPerPacket == 0); e.m_SamplesPerChunk = e.m_SamplesPerChunk / SamplesPerPacket; e.m_FirstSample = (e.m_FirstSample-1) / SamplesPerPacket + 1; } AP4_ASSERT(m_StszAtom->m_SampleCount % SamplesPerPacket == 0); m_StszAtom->m_SampleCount = m_StszAtom->m_SampleCount / SamplesPerPacket; m_StszAtom->m_SampleSize = BytesPerFrame; AP4_ASSERT(m_SttsAtom->m_Entries.ItemCount() == 1); m_SttsAtom->m_Entries[0].m_SampleCount = m_StszAtom->m_SampleCount; m_SttsAtom->m_Entries[0].m_SampleDuration = SamplesPerPacket; } } } } }
/*---------------------------------------------------------------------- | AP4_Expandable::Write +---------------------------------------------------------------------*/ AP4_Result AP4_Expandable::Write(AP4_ByteStream& stream) { AP4_Result result; // write the class id switch (m_ClassIdSize) { case CLASS_ID_SIZE_08: result = stream.WriteUI08((AP4_UI08)m_ClassId); if (AP4_FAILED(result)) return result; break; default: return AP4_ERROR_INTERNAL; } // write the size AP4_ASSERT(m_HeaderSize-1 <= 8); AP4_ASSERT(m_HeaderSize >= 2); unsigned int size = m_PayloadSize; unsigned char bytes[8]; // last bytes of the encoded size bytes[m_HeaderSize-2] = size&0x7F; // leading bytes of the encoded size for (int i=m_HeaderSize-3; i>=0; i--) { // move to the next 7 bits size >>= 7; // output a byte with a top bit marker bytes[i] = (size&0x7F) | 0x80; } result = stream.Write(bytes, m_HeaderSize-1); if (AP4_FAILED(result)) return result; // write the fields WriteFields(stream); return result; }
/*---------------------------------------------------------------------- | AP4_Stream::Write +---------------------------------------------------------------------*/ AP4_Result AP4_ByteStream::Write(const void* buffer, AP4_Size bytes_to_write) { // shortcut if (bytes_to_write == 0) return AP4_SUCCESS; // write until failure AP4_Size bytes_written; while (bytes_to_write) { AP4_Result result = WritePartial(buffer, bytes_to_write, bytes_written); if (AP4_FAILED(result)) return result; if (bytes_written == 0) return AP4_ERROR_INTERNAL; AP4_ASSERT(bytes_written <= bytes_to_write); bytes_to_write -= bytes_written; buffer = (const void*)(((const AP4_Byte*)buffer)+bytes_written); } return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | AP4_ByteStream::Read +---------------------------------------------------------------------*/ AP4_Result AP4_ByteStream::Read(void* buffer, AP4_Size bytes_to_read) { // shortcut if (bytes_to_read == 0) return AP4_SUCCESS; // read until failure AP4_Size bytes_read; while (bytes_to_read) { AP4_Result result = ReadPartial(buffer, bytes_to_read, bytes_read); if (AP4_FAILED(result)) return result; if (bytes_read == 0) return AP4_ERROR_INTERNAL; AP4_ASSERT(bytes_read <= bytes_to_read); bytes_to_read -= bytes_read; buffer = (void*)(((AP4_Byte*)buffer)+bytes_read); } return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | 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_CbcStreamCipher::DecryptBuffer +---------------------------------------------------------------------*/ AP4_Result AP4_CbcStreamCipher::DecryptBuffer(const AP4_UI08* in, AP4_Size in_size, AP4_UI08* out, AP4_Size* out_size, bool is_last_buffer) { // we don't do much checking here because this method is only called // from ProcessBuffer(), which does all the parameter checking // deal chain-block buffering if (m_ChainBlockFullness != AP4_CIPHER_BLOCK_SIZE) { unsigned int needed = AP4_CIPHER_BLOCK_SIZE-m_ChainBlockFullness; unsigned int chunk = (in_size > needed) ? needed : in_size; AP4_CopyMemory(&m_ChainBlock[m_ChainBlockFullness], in, chunk); in_size -= chunk; in += chunk; m_ChainBlockFullness += chunk; m_StreamOffset += chunk; if (m_ChainBlockFullness != AP4_CIPHER_BLOCK_SIZE) { // not enough to continue *out_size = 0; return AP4_SUCCESS; } } AP4_ASSERT(m_ChainBlockFullness == AP4_CIPHER_BLOCK_SIZE); // compute how many blocks we span AP4_UI64 start_block = (m_StreamOffset-m_InBlockFullness)/AP4_CIPHER_BLOCK_SIZE; AP4_UI64 end_block = (m_StreamOffset+in_size)/AP4_CIPHER_BLOCK_SIZE; AP4_UI32 blocks_needed = (AP4_UI32)(end_block-start_block); // compute how many blocks we will need to produce if (*out_size < blocks_needed*AP4_CIPHER_BLOCK_SIZE) { *out_size = blocks_needed*AP4_CIPHER_BLOCK_SIZE; return AP4_ERROR_BUFFER_TOO_SMALL; } *out_size = blocks_needed*AP4_CIPHER_BLOCK_SIZE; if (blocks_needed && m_OutputSkip) *out_size -= m_OutputSkip; // shortcut if (in_size == 0) return AP4_SUCCESS; // deal with in-block buffering // NOTE: if we have bytes to skip on output, always use the in-block buffer for // the first block, even if we're aligned, this makes the code simpler AP4_ASSERT(m_InBlockFullness < AP4_CIPHER_BLOCK_SIZE); if (m_OutputSkip || m_InBlockFullness) { unsigned int needed = AP4_CIPHER_BLOCK_SIZE-m_InBlockFullness; unsigned int chunk = (in_size > needed) ? needed : in_size; AP4_CopyMemory(&m_InBlock[m_InBlockFullness], in, chunk); in_size -= chunk; in += chunk; m_InBlockFullness += chunk; m_StreamOffset += chunk; if (m_InBlockFullness != AP4_CIPHER_BLOCK_SIZE) { // not enough to continue *out_size = 0; return AP4_SUCCESS; } AP4_UI08 out_block[AP4_CIPHER_BLOCK_SIZE]; AP4_Result result = m_BlockCipher->Process(m_InBlock, AP4_CIPHER_BLOCK_SIZE, out_block, m_ChainBlock); m_InBlockFullness = 0; if (AP4_FAILED(result)) { *out_size = 0; return result; } AP4_CopyMemory(m_ChainBlock, m_InBlock, AP4_CIPHER_BLOCK_SIZE); if (m_OutputSkip) { AP4_ASSERT(m_OutputSkip < AP4_CIPHER_BLOCK_SIZE); AP4_CopyMemory(out, &out_block[m_OutputSkip], AP4_CIPHER_BLOCK_SIZE-m_OutputSkip); out += AP4_CIPHER_BLOCK_SIZE-m_OutputSkip; m_OutputSkip = 0; } else { AP4_CopyMemory(out, out_block, AP4_CIPHER_BLOCK_SIZE); out += AP4_CIPHER_BLOCK_SIZE; } } AP4_ASSERT(m_InBlockFullness == 0); AP4_ASSERT(m_OutputSkip == 0); // process full blocks unsigned int block_count = in_size/AP4_CIPHER_BLOCK_SIZE; if (block_count) { AP4_UI32 blocks_size = block_count*AP4_CIPHER_BLOCK_SIZE; AP4_Result result = m_BlockCipher->Process(in, blocks_size, out, m_ChainBlock); AP4_CopyMemory(m_ChainBlock, in+blocks_size-AP4_CIPHER_BLOCK_SIZE, AP4_CIPHER_BLOCK_SIZE); if (AP4_FAILED(result)) { *out_size = 0; return result; } in += blocks_size; out += blocks_size; in_size -= blocks_size; m_StreamOffset += blocks_size; } // buffer partial block leftovers if (in_size) { AP4_ASSERT(in_size < AP4_CIPHER_BLOCK_SIZE); AP4_CopyMemory(m_InBlock, in, in_size); m_InBlockFullness = in_size; m_StreamOffset += in_size; } // deal with padding if (is_last_buffer) { AP4_UI08 pad_byte = *(out-1); if (pad_byte > AP4_CIPHER_BLOCK_SIZE || *out_size < pad_byte) { *out_size = 0; return AP4_ERROR_INVALID_FORMAT; } *out_size -= pad_byte; } return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | AP4_CbcStreamCipher::EncryptBuffer +---------------------------------------------------------------------*/ AP4_Result AP4_CbcStreamCipher::EncryptBuffer(const AP4_UI08* in, AP4_Size in_size, AP4_UI08* out, AP4_Size* out_size, bool is_last_buffer) { // we don't do much checking here because this method is only called // from ProcessBuffer(), which does all the parameter checking // compute how many blocks we span AP4_UI64 start_block = (m_StreamOffset-m_InBlockFullness)/AP4_CIPHER_BLOCK_SIZE; AP4_UI64 end_block = (m_StreamOffset+in_size)/AP4_CIPHER_BLOCK_SIZE; AP4_UI32 blocks_needed = (AP4_UI32)(end_block-start_block); // compute how many blocks we will need to produce if (is_last_buffer) { ++blocks_needed; } if (*out_size < blocks_needed*AP4_CIPHER_BLOCK_SIZE) { *out_size = blocks_needed*AP4_CIPHER_BLOCK_SIZE; return AP4_ERROR_BUFFER_TOO_SMALL; } *out_size = blocks_needed*AP4_CIPHER_BLOCK_SIZE; // finish any incomplete block from a previous call unsigned int offset = (unsigned int)(m_StreamOffset%AP4_CIPHER_BLOCK_SIZE); AP4_ASSERT(m_InBlockFullness == offset); if (offset) { unsigned int chunk = AP4_CIPHER_BLOCK_SIZE-offset; if (chunk > in_size) chunk = in_size; for (unsigned int x=0; x<chunk; x++) { m_InBlock[x+offset] = in[x]; } in += chunk; in_size -= chunk; m_StreamOffset += chunk; m_InBlockFullness += chunk; if (offset+chunk == AP4_CIPHER_BLOCK_SIZE) { // we have filled the input block, encrypt it AP4_Result result = m_BlockCipher->Process(m_InBlock, AP4_CIPHER_BLOCK_SIZE, out, m_ChainBlock); AP4_CopyMemory(m_ChainBlock, out, AP4_CIPHER_BLOCK_SIZE); m_InBlockFullness = 0; if (AP4_FAILED(result)) { *out_size = 0; return result; } out += AP4_CIPHER_BLOCK_SIZE; } } // encrypt the whole blocks unsigned int block_count = in_size/AP4_CIPHER_BLOCK_SIZE; if (block_count) { AP4_ASSERT(m_InBlockFullness == 0); AP4_UI32 blocks_size = block_count*AP4_CIPHER_BLOCK_SIZE; AP4_Result result = m_BlockCipher->Process(in, blocks_size, out, m_ChainBlock); AP4_CopyMemory(m_ChainBlock, out+blocks_size-AP4_CIPHER_BLOCK_SIZE, AP4_CIPHER_BLOCK_SIZE); if (AP4_FAILED(result)) { *out_size = 0; return result; } in += blocks_size; out += blocks_size; in_size -= blocks_size; m_StreamOffset += blocks_size; } // deal with what's left if (in_size) { AP4_ASSERT(in_size < AP4_CIPHER_BLOCK_SIZE); for (unsigned int x=0; x<in_size; x++) { m_InBlock[x+m_InBlockFullness] = in[x]; } m_InBlockFullness += in_size; m_StreamOffset += in_size; } // pad if needed if (is_last_buffer) { AP4_ASSERT(m_InBlockFullness == m_StreamOffset%AP4_CIPHER_BLOCK_SIZE); AP4_UI08 pad_byte = AP4_CIPHER_BLOCK_SIZE-(AP4_UI08)(m_StreamOffset%AP4_CIPHER_BLOCK_SIZE); for (unsigned int x=AP4_CIPHER_BLOCK_SIZE-pad_byte; x<AP4_CIPHER_BLOCK_SIZE; x++) { m_InBlock[x] = pad_byte; } AP4_Result result = m_BlockCipher->Process(m_InBlock, AP4_CIPHER_BLOCK_SIZE, out, m_ChainBlock); AP4_CopyMemory(m_ChainBlock, out, AP4_CIPHER_BLOCK_SIZE); m_InBlockFullness = 0; if (AP4_FAILED(result)) { *out_size = 0; return result; } } return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | AP4_StscAtom::GetChunkForSample +---------------------------------------------------------------------*/ AP4_Result AP4_StscAtom::GetChunkForSample(AP4_Ordinal sample, AP4_Ordinal& chunk, AP4_Ordinal& skip, AP4_Ordinal& sample_description_index) { // preconditions AP4_ASSERT(sample > 0); // decide whether to start the search from the cached index // or from the start AP4_Ordinal group; if (m_CachedChunkGroup < m_Entries.ItemCount() && m_Entries[m_CachedChunkGroup].m_FirstSample <= sample) { group = m_CachedChunkGroup; } else { group = 0; } // find which group of chunk contains this one while (group < m_Entries.ItemCount()) { AP4_Cardinal sample_count = m_Entries[group].m_ChunkCount*m_Entries[group].m_SamplesPerChunk; if (sample_count == 0) { // unlimited samples in this group (last group) if (m_Entries[group].m_FirstSample > sample) { // something is wrong return AP4_ERROR_INVALID_FORMAT; } } else { // normal group if (m_Entries[group].m_FirstSample + sample_count <= sample) { // the sample is not in this group group++; continue; } } // the sample is in this group if (m_Entries[group].m_SamplesPerChunk == 0) { // something is wrong return AP4_ERROR_INVALID_FORMAT; } unsigned int chunk_offset = ((sample-m_Entries[group].m_FirstSample) / m_Entries[group].m_SamplesPerChunk); chunk = m_Entries[group].m_FirstChunk + chunk_offset; skip = sample - (m_Entries[group].m_FirstSample + m_Entries[group].m_SamplesPerChunk*chunk_offset); sample_description_index = m_Entries[group].m_SampleDescriptionIndex; // cache the result (to accelerate finding the right group // next time around) m_CachedChunkGroup = group; return AP4_SUCCESS; } // chunk not found chunk = 0; skip = 0; sample_description_index = 0; return AP4_ERROR_OUT_OF_RANGE; }
/*---------------------------------------------------------------------- | AP4_SampleTable::GenerateStblAtom +---------------------------------------------------------------------*/ AP4_Result AP4_SampleTable::GenerateStblAtom(AP4_ContainerAtom*& stbl) { // create the stbl container stbl = new AP4_ContainerAtom(AP4_ATOM_TYPE_STBL); // create the stsd atom AP4_StsdAtom* stsd = new AP4_StsdAtom(this); // create the stsz atom AP4_StszAtom* stsz = new AP4_StszAtom(); // create the stsc atom AP4_StscAtom* stsc = new AP4_StscAtom(); // create the stts atom AP4_SttsAtom* stts = new AP4_SttsAtom(); // create the stss atom AP4_StssAtom* stss = new AP4_StssAtom(); // declare the ctts atom (may be created later) AP4_CttsAtom* ctts = NULL; // start chunk table AP4_Ordinal current_chunk_index = 0; AP4_Size current_chunk_size = 0; AP4_Position current_chunk_offset = 0; AP4_Cardinal current_samples_in_chunk = 0; AP4_Ordinal current_sample_description_index = 0; AP4_UI32 current_duration = 0; AP4_Cardinal current_duration_run = 0; AP4_UI32 current_cts_delta = 0; AP4_Cardinal current_cts_delta_run = 0; AP4_Array<AP4_Position> chunk_offsets; // process all the samples bool all_samples_are_sync = false; AP4_Cardinal sample_count = GetSampleCount(); for (AP4_Ordinal i=0; i<sample_count; i++) { AP4_Sample sample; GetSample(i, sample); // update DTS table AP4_UI32 new_duration = sample.GetDuration(); if (new_duration != current_duration && current_duration_run != 0) { // emit a new stts entry stts->AddEntry(current_duration_run, current_duration); // reset the run count current_duration_run = 0; } ++current_duration_run; current_duration = new_duration; // update CTS table AP4_UI32 new_cts_delta = sample.GetCtsDelta(); if (new_cts_delta != current_cts_delta && current_cts_delta_run != 0) { // create a ctts atom if we don't have one if (ctts == NULL) ctts = new AP4_CttsAtom(); //emit a new ctts entry ctts->AddEntry(current_cts_delta_run, current_cts_delta); // reset the run count current_cts_delta_run = 0; } ++current_cts_delta_run; current_cts_delta = new_cts_delta; // add an entry into the stsz atom stsz->AddEntry(sample.GetSize()); // update the sync sample table if (sample.IsSync()) { stss->AddEntry(i+1); if (i==0) all_samples_are_sync = true; } else { all_samples_are_sync = false; } // see in which chunk this sample is AP4_Ordinal chunk_index = 0; AP4_Ordinal position_in_chunk = 0; AP4_Result result = GetSampleChunkPosition(i, chunk_index, position_in_chunk); if (AP4_SUCCEEDED(result)) { if (chunk_index != current_chunk_index && current_samples_in_chunk != 0) { // new chunk chunk_offsets.Append(current_chunk_offset); current_chunk_offset += current_chunk_size; stsc->AddEntry(1, current_samples_in_chunk, current_sample_description_index+1); current_samples_in_chunk = 0; current_chunk_size = 0; } current_chunk_index = chunk_index; } // store the sample description index current_sample_description_index = sample.GetDescriptionIndex(); // adjust the current chunk info current_chunk_size += sample.GetSize(); ++current_samples_in_chunk; } // finish the stts table if (sample_count) stts->AddEntry(current_duration_run, current_duration); // finish the ctts table if we have one if (ctts) { AP4_ASSERT(current_cts_delta_run != 0); // add a ctts entry ctts->AddEntry(current_cts_delta_run, current_cts_delta); } // process any unfinished chunk if (current_samples_in_chunk != 0) { // new chunk chunk_offsets.Append(current_chunk_offset); stsc->AddEntry(1, current_samples_in_chunk, current_sample_description_index+1); } // attach the children of stbl stbl->AddChild(stsd); stbl->AddChild(stsz); stbl->AddChild(stsc); stbl->AddChild(stts); if (ctts) stbl->AddChild(ctts); if (!all_samples_are_sync && stss->GetEntries().ItemCount() != 0) { stbl->AddChild(stss); } else { delete stss; } // see if we need a co64 or an stco atom AP4_Size chunk_count = chunk_offsets.ItemCount(); if (current_chunk_offset <= 0xFFFFFFFF) { // make an array of 32-bit entries AP4_UI32* chunk_offsets_32 = new AP4_UI32[chunk_count]; for (unsigned int i=0; i<chunk_count; i++) { chunk_offsets_32[i] = (AP4_UI32)chunk_offsets[i]; } // create the stco atom AP4_StcoAtom* stco = new AP4_StcoAtom(&chunk_offsets_32[0], chunk_count); stbl->AddChild(stco); delete[] chunk_offsets_32; } else { // create the co64 atom AP4_Co64Atom* co64 = new AP4_Co64Atom(&chunk_offsets[0], chunk_count); stbl->AddChild(co64); } 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; }
/*---------------------------------------------------------------------- | AP4_Processor::ProcessFragments +---------------------------------------------------------------------*/ AP4_Result AP4_Processor::ProcessFragment( AP4_ContainerAtom* moof, AP4_SidxAtom* sidx, AP4_Position sidx_position, AP4_ByteStream& output, AP4_Array<AP4_Position>& moof_positions, AP4_Array<AP4_Position>& mdat_positions) { unsigned int fragment_index = 0; //AP4_UI64 mdat_payload_offset = atom_offset+atom->GetSize()+AP4_ATOM_HEADER_SIZE; AP4_Sample sample; AP4_DataBuffer sample_data_in; AP4_DataBuffer sample_data_out; AP4_Result result = AP4_SUCCESS; // parse the moof //AP4_MovieFragment* fragment = new AP4_MovieFragment(moof); // process all the traf atoms AP4_Array<AP4_Processor::FragmentHandler*> handlers; AP4_Array<AP4_FragmentSampleTable*> sample_tables; for (; AP4_Atom* child = moof->GetChild(AP4_ATOM_TYPE_TRAF, handlers.ItemCount());) { AP4_TrafAtom* traf = AP4_DYNAMIC_CAST(AP4_TrafAtom, child); PERTRACK &track_data(m_TrackData[traf->GetInternalTrackId()]); AP4_TrakAtom* trak = track_data.track_handler->GetTrakAtom(); AP4_TrexAtom* trex = track_data.track_handler->GetTrexAtom(); // create the handler for this traf AP4_Processor::FragmentHandler* handler = CreateFragmentHandler(trak, trex, traf, *(m_StreamData[track_data.streamId].stream), moof_positions[track_data.streamId]); if (handler) { result = handler->ProcessFragment(); if (AP4_FAILED(result)) return result; } handlers.Append(handler); // create a sample table object so we can read the sample data AP4_FragmentSampleTable* sample_table = new AP4_FragmentSampleTable( traf, trex, traf->GetInternalTrackId(), m_StreamData[track_data.streamId].stream, moof_positions[traf->GetInternalTrackId()], mdat_positions[traf->GetInternalTrackId()], 0); sample_tables.Append(sample_table); // let the handler look at the samples before we process them if (handler) result = handler->PrepareForSamples(sample_table); if (AP4_FAILED(result)) return result; } output.Buffer(); // write the moof AP4_UI64 moof_out_start = 0; output.Tell(moof_out_start); moof->Write(output); // remember the location of this fragment FragmentMapEntry map_entry = { moof_positions[0], moof_out_start }; fragment_map_.Append(map_entry); // write an mdat header AP4_Position mdat_out_start; AP4_UI64 mdat_size = AP4_ATOM_HEADER_SIZE; output.Tell(mdat_out_start); output.WriteUI32(0); output.WriteUI32(AP4_ATOM_TYPE_MDAT); // process all track runs for (unsigned int i=0; i<handlers.ItemCount(); i++) { 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)); if (traf == NULL) continue; AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, traf->GetChild(AP4_ATOM_TYPE_TFHD)); // compute the base data offset AP4_UI64 base_data_offset; if (tfhd->GetFlags() & AP4_TFHD_FLAG_BASE_DATA_OFFSET_PRESENT) { base_data_offset = mdat_out_start+AP4_ATOM_HEADER_SIZE; } else { base_data_offset = moof_out_start; } // build a list of all trun atoms AP4_Array<AP4_TrunAtom*> truns; 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); truns.Append(trun); } } AP4_Ordinal trun_index = 0; AP4_Ordinal trun_sample_index = 0; AP4_TrunAtom* trun = truns[0]; trun->SetDataOffset((AP4_SI32)((mdat_out_start+mdat_size)-base_data_offset)); // write the mdat for (unsigned int j=0; j<sample_tables[i]->GetSampleCount(); j++, trun_sample_index++) { // advance the trun index if necessary if (trun_sample_index >= trun->GetEntries().ItemCount()) { trun = truns[++trun_index]; trun->SetDataOffset((AP4_SI32)((mdat_out_start+mdat_size)-base_data_offset)); trun_sample_index = 0; } // get the next sample result = sample_tables[i]->GetSample(j, sample); if (AP4_FAILED(result)) return result; sample.ReadData(sample_data_in); m_TrackData[sample_tables[i]->GetInteralTrackId()].dts = sample.GetDts(); // 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; // update the mdat size mdat_size += sample_data_out.GetDataSize(); // update the trun entry trun->UseEntries()[trun_sample_index].sample_size = sample_data_out.GetDataSize(); } else { // write the sample data (unmodified) result = output.Write(sample_data_in.GetData(), sample_data_in.GetDataSize()); if (AP4_FAILED(result)) return result; // update the mdat size mdat_size += sample_data_in.GetDataSize(); } } if (handler) { // update the tfhd header if (tfhd->GetFlags() & AP4_TFHD_FLAG_BASE_DATA_OFFSET_PRESENT) { tfhd->SetBaseDataOffset(mdat_out_start+AP4_ATOM_HEADER_SIZE); } if (tfhd->GetFlags() & AP4_TFHD_FLAG_DEFAULT_SAMPLE_SIZE_PRESENT) { tfhd->SetDefaultSampleSize(trun->GetEntries()[0].sample_size); } // give the handler a chance to update the atoms handler->FinishFragment(); } } // update the mdat header AP4_Position mdat_out_end; output.Tell(mdat_out_end); #if defined(AP4_DEBUG) AP4_ASSERT(mdat_out_end-mdat_out_start == mdat_size); #endif if (AP4_FAILED(result = output.Seek(mdat_out_start))) return result; output.WriteUI32((AP4_UI32)mdat_size); output.Seek(mdat_out_end); // update the moof if needed if (AP4_FAILED(result = output.Seek(moof_out_start))) return result; moof->Write(output); output.Seek(mdat_out_end); // update the sidx if we have one if (sidx && fragment_index < sidx->GetReferences().ItemCount()) { if (fragment_index == 0) { sidx->SetFirstOffset(moof_out_start-(sidx_position+sidx->GetSize())); } AP4_LargeSize fragment_size = mdat_out_end-moof_out_start; AP4_SidxAtom::Reference& sidx_ref = sidx->UseReferences()[fragment_index]; sidx_ref.m_ReferencedSize = (AP4_UI32)fragment_size; } // cleanup //delete fragment; for (unsigned int i=0; i<handlers.ItemCount(); i++) { delete handlers[i]; } for (unsigned int i=0; i<sample_tables.ItemCount(); i++) { delete sample_tables[i]; } if (AP4_FAILED(result = output.Flush())) return result; 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; }
/*---------------------------------------------------------------------- | 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; }