/***************************************************************** | | AP4 - Linear Sample Reader | | Copyright 2002-2009 Axiomatic Systems, LLC | | | This file is part of Bento4/AP4 (MP4 Atom Processing Library). | | Unless you have obtained Bento4 under a difference license, | this version of Bento4 is Bento4|GPL. | Bento4|GPL is free software; you can redistribute it and/or modify | it under the terms of the GNU General Public License as published by | the Free Software Foundation; either version 2, or (at your option) | any later version. | | Bento4|GPL is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | You should have received a copy of the GNU General Public License | along with Bento4|GPL; see the file COPYING. If not, write to the | Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA | 02111-1307, USA. | ****************************************************************/ /*---------------------------------------------------------------------- | includes +---------------------------------------------------------------------*/ #include "Ap4LinearReader.h" #include "Ap4Array.h" #include "Ap4SampleTable.h" #include "Ap4MovieFragment.h" #include "Ap4FragmentSampleTable.h" #include "Ap4AtomFactory.h" #include "Ap4TfraAtom.h" /*---------------------------------------------------------------------- | AP4_LinearReader::AP4_LinearReader +---------------------------------------------------------------------*/ AP4_LinearReader::AP4_LinearReader(AP4_Movie& movie, AP4_ByteStream* fragment_stream) : m_Movie(movie), m_Fragment(NULL), m_FragmentStream(fragment_stream), m_CurrentFragmentPosition(0), m_NextFragmentPosition(0), m_BufferFullness(0), m_BufferFullnessPeak(0), m_Mfra(NULL) { m_HasFragments = movie.HasFragments(); if (fragment_stream) { fragment_stream->AddReference(); fragment_stream->Tell(m_CurrentFragmentPosition); m_NextFragmentPosition = m_CurrentFragmentPosition; } } /*---------------------------------------------------------------------- | AP4_LinearReader::~AP4_LinearReader +---------------------------------------------------------------------*/ AP4_LinearReader::~AP4_LinearReader() { for (unsigned int i=0; iRelease(); } /*---------------------------------------------------------------------- | AP4_LinearReader::EnableTrack +---------------------------------------------------------------------*/ AP4_Result AP4_LinearReader::EnableTrack(AP4_UI32 track_id) { // check if we don't already have this if (FindTracker(track_id)) return AP4_SUCCESS; // find the track in the movie AP4_Track* track = m_Movie.GetTrack(track_id); if (track == NULL) return AP4_ERROR_NO_SUCH_ITEM; // process this track return ProcessTrack(track); } /*---------------------------------------------------------------------- | AP4_LinearReader::FlushQueue +---------------------------------------------------------------------*/ void AP4_LinearReader::FlushQueue(Tracker* tracker) { // empty any queued samples for (AP4_List::Item* item = tracker->m_Samples.FirstItem(); item; item = item->GetNext()) { SampleBuffer* buffer = item->GetData(); m_BufferFullness -= buffer->m_Data.GetDataSize(); delete buffer; } tracker->m_Samples.Clear(); } /*---------------------------------------------------------------------- | AP4_LinearReader::FlushQueues +---------------------------------------------------------------------*/ void AP4_LinearReader::FlushQueues() { for (unsigned int i=0; im_SampleTable); delete tracker->m_NextSample; tracker->m_NextSample = NULL; if (sample_index >= tracker->m_SampleTable->GetSampleCount()) { return AP4_ERROR_OUT_OF_RANGE; } tracker->m_Eos = false; tracker->m_NextSampleIndex = sample_index; // empty any queued samples for (AP4_List::Item* item = tracker->m_Samples.FirstItem(); item; item = item->GetNext()) { SampleBuffer* buffer = item->GetData(); m_BufferFullness -= buffer->m_Data.GetDataSize(); delete buffer; } tracker->m_Samples.Clear(); 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 atom_factory; atom_factory.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::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& 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 (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; im_SampleTableIsOwned) { 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_LinearReader::ProcessTrack +---------------------------------------------------------------------*/ AP4_Result AP4_LinearReader::ProcessTrack(AP4_Track* track) { // create a new entry for the track Tracker* tracker = new Tracker(track); tracker->m_SampleTable = track->GetSampleTable(); return m_Trackers.Append(tracker); } /*---------------------------------------------------------------------- | AP4_LinearReader::ProcessMoof +---------------------------------------------------------------------*/ AP4_Result AP4_LinearReader::ProcessMoof(AP4_ContainerAtom* moof, AP4_Position moof_offset, AP4_Position mdat_payload_offset) { AP4_Result result; // create a new fragment delete m_Fragment; m_Fragment = new AP4_MovieFragment(moof); // update the trackers AP4_Array ids; m_Fragment->GetTrackIds(ids); for (unsigned int i=0; im_SampleTableIsOwned) { delete tracker->m_SampleTable; } tracker->m_SampleTable = NULL; tracker->m_NextSampleIndex = 0; for (unsigned int j=0; jm_Track->GetId()) { AP4_FragmentSampleTable* sample_table = NULL; result = m_Fragment->CreateSampleTable(&m_Movie, ids[j], m_FragmentStream, moof_offset, mdat_payload_offset, tracker->m_NextDts, sample_table); if (AP4_FAILED(result)) return result; tracker->m_SampleTable = sample_table; tracker->m_SampleTableIsOwned = true; tracker->m_Eos = false; break; } } } return AP4_SUCCESS; } /*---------------------------------------------------------------------- | 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; m_CurrentFragmentPosition = m_NextFragmentPosition; // read atoms until we find a moof assert(m_HasFragments); if (!m_FragmentStream) return AP4_ERROR_INVALID_STATE; AP4_DefaultAtomFactory atom_factory; do { AP4_Atom* atom = NULL; AP4_Position last_position = 0; m_FragmentStream->Tell(last_position); result = atom_factory.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 the moof started m_CurrentFragmentPosition = last_position; // 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_LinearReader::Advance +---------------------------------------------------------------------*/ AP4_Result AP4_LinearReader::Advance(bool read_data) { AP4_UI64 min_offset = (AP4_UI64)(-1); Tracker* next_tracker = NULL; for (;;) { for (unsigned int i=0; im_Eos) continue; if (tracker->m_SampleTable == NULL) continue; // get the next sample unless we have it already if (tracker->m_NextSample == NULL) { if (tracker->m_NextSampleIndex >= tracker->m_SampleTable->GetSampleCount()) { if (!m_HasFragments) tracker->m_Eos = true; if (tracker->m_SampleTableIsOwned) { delete tracker->m_SampleTable; tracker->m_SampleTable = NULL; } continue; } tracker->m_NextSample = new AP4_Sample(); AP4_Result result = tracker->m_SampleTable->GetSample(tracker->m_NextSampleIndex, *tracker->m_NextSample); if (AP4_FAILED(result)) { tracker->m_Eos = true; delete tracker->m_NextSample; tracker->m_NextSample = NULL; continue; } tracker->m_NextDts += tracker->m_NextSample->GetDuration(); } assert(tracker->m_NextSample); AP4_UI64 offset = tracker->m_NextSample->GetOffset(); if (offset < min_offset) { min_offset = offset; next_tracker = tracker; } } if (next_tracker) break; if (m_HasFragments) { AP4_Result result = AdvanceFragment(); if (AP4_FAILED(result)) return result; } else { break; } } if (next_tracker) { // read the sample into a buffer assert(next_tracker->m_NextSample); SampleBuffer* buffer = new SampleBuffer(next_tracker->m_NextSample); AP4_Result result; if (read_data) { if (next_tracker->m_Reader) { result = next_tracker->m_Reader->ReadSampleData(*buffer->m_Sample, buffer->m_Data); } else { result = buffer->m_Sample->ReadData(buffer->m_Data); } if (AP4_FAILED(result)) { delete buffer; return result; } // detach the sample from its source now that we've read its data buffer->m_Sample->Detach(); } // add the buffer to the queue next_tracker->m_Samples.Add(buffer); m_BufferFullness += buffer->m_Data.GetDataSize(); if (m_BufferFullness > m_BufferFullnessPeak) { m_BufferFullnessPeak = m_BufferFullness; } next_tracker->m_NextSample = NULL; next_tracker->m_NextSampleIndex++; return AP4_SUCCESS; } return AP4_ERROR_EOS; } /*---------------------------------------------------------------------- | AP4_LinearReader::PopSample +---------------------------------------------------------------------*/ bool AP4_LinearReader::PopSample(Tracker* tracker, AP4_Sample& sample, AP4_DataBuffer* sample_data) { SampleBuffer* head = NULL; if (AP4_SUCCEEDED(tracker->m_Samples.PopHead(head)) && head) { assert(head->m_Sample); sample = *head->m_Sample; if (sample_data) { sample_data->SetData(head->m_Data.GetData(), head->m_Data.GetDataSize()); } assert(m_BufferFullness >= head->m_Data.GetDataSize()); m_BufferFullness -= head->m_Data.GetDataSize(); delete head; return true; } return false; } /*---------------------------------------------------------------------- | AP4_LinearReader::ReadNextSample +---------------------------------------------------------------------*/ AP4_Result AP4_LinearReader::ReadNextSample(AP4_UI32 track_id, AP4_Sample& sample, AP4_DataBuffer& sample_data) { if (m_Trackers.ItemCount() == 0) { return AP4_ERROR_NO_SUCH_ITEM; } // look for a sample from a specific track Tracker* tracker = FindTracker(track_id); if (tracker == NULL) return AP4_ERROR_INVALID_PARAMETERS; for(;;) { // pop a sample if we can if (PopSample(tracker, sample, &sample_data)) return AP4_SUCCESS; // don't continue if we've reached the end of that tracker if (tracker->m_Eos) return AP4_ERROR_EOS; AP4_Result result = Advance(); if (AP4_FAILED(result)) return result; } // unreachable - return AP4_ERROR_EOS; } /*---------------------------------------------------------------------- | AP4_LinearReader::ReadNextSample +---------------------------------------------------------------------*/ AP4_Result AP4_LinearReader::ReadNextSample(AP4_Sample& sample, AP4_DataBuffer* sample_data, AP4_UI32& track_id) { if (m_Trackers.ItemCount() == 0) { track_id = 0; return AP4_ERROR_NO_SUCH_ITEM; } // return the oldest buffered sample, if any AP4_UI64 min_offset = (AP4_UI64)(-1); Tracker* next_tracker = NULL; for (;;) { for (unsigned int i=0; im_Eos) continue; AP4_List::Item* item = tracker->m_Samples.FirstItem(); if (item) { AP4_UI64 offset = item->GetData()->m_Sample->GetOffset(); if (offset < min_offset) { min_offset = offset; next_tracker = tracker; } } } // return the sample if we have found a tracker if (next_tracker) { PopSample(next_tracker, sample, sample_data); track_id = next_tracker->m_Track->GetId(); return AP4_SUCCESS; } // nothing found, read one more sample AP4_Result result = Advance(sample_data != NULL); if (AP4_FAILED(result)) return result; } // unreachable - return AP4_ERROR_EOS; } /*---------------------------------------------------------------------- | AP4_LinearReader::ReadNextSample +---------------------------------------------------------------------*/ AP4_Result AP4_LinearReader::ReadNextSample(AP4_Sample& sample, AP4_DataBuffer& sample_data, AP4_UI32& track_id) { return ReadNextSample(sample, &sample_data, track_id); } /*---------------------------------------------------------------------- | AP4_LinearReader::GetNextSample +---------------------------------------------------------------------*/ AP4_Result AP4_LinearReader::GetNextSample(AP4_Sample& sample, AP4_UI32& track_id) { return ReadNextSample(sample, NULL, track_id); } /*---------------------------------------------------------------------- | AP4_LinearReader::FindTracker +---------------------------------------------------------------------*/ AP4_LinearReader::Tracker* AP4_LinearReader::FindTracker(AP4_UI32 track_id) { for (unsigned int i=0; im_Track->GetId() == track_id) return m_Trackers[i]; } // not found return NULL; } /*---------------------------------------------------------------------- | AP4_LinearReader::Tracker::~Tracker +---------------------------------------------------------------------*/ AP4_LinearReader::Tracker::~Tracker() { if (m_SampleTableIsOwned) delete m_SampleTable; delete m_Reader; } /*---------------------------------------------------------------------- | AP4_DecryptingSampleReader::ReadSampleData +---------------------------------------------------------------------*/ AP4_Result AP4_DecryptingSampleReader::ReadSampleData(AP4_Sample& sample, AP4_DataBuffer& sample_data) { AP4_Result result = sample.ReadData(m_DataBuffer); if (AP4_FAILED(result)) return result; return m_Decrypter->DecryptSampleData(m_DataBuffer, sample_data); }