From e23d6a15a6d48d86b89fe62a5ababb9397263a4f Mon Sep 17 00:00:00 2001 From: shatty Date: Tue, 10 Feb 2004 11:01:47 +0000 Subject: [PATCH] speedy duration computation, seek to zero, still missing the samples from the initial packet though? git-svn-id: file:///srv/svn/repos/haiku/trunk/current@6551 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- .../media/plugins/ogg/OggReaderPlugin.cpp | 97 ++++++++++++++++- .../media/plugins/ogg/OggReaderPlugin.h | 2 + src/add-ons/media/plugins/ogg/OggSeekable.cpp | 100 ++++++++++++++++-- src/add-ons/media/plugins/ogg/OggSeekable.h | 9 ++ .../media/plugins/ogg/OggSpeexSeekable.cpp | 49 ++++++++- .../media/plugins/ogg/OggTobiasSeekable.cpp | 54 +++++++++- .../media/plugins/ogg/OggTobiasSeekable.h | 3 + .../media/plugins/ogg/OggVorbisSeekable.cpp | 43 +++++++- 8 files changed, 336 insertions(+), 21 deletions(-) diff --git a/src/add-ons/media/plugins/ogg/OggReaderPlugin.cpp b/src/add-ons/media/plugins/ogg/OggReaderPlugin.cpp index f22573693b..8ec331ccf2 100644 --- a/src/add-ons/media/plugins/ogg/OggReaderPlugin.cpp +++ b/src/add-ons/media/plugins/ogg/OggReaderPlugin.cpp @@ -89,7 +89,8 @@ retry: return B_ERROR; } long serialno = ogg_page_serialno(&page); - if (fTracks.find(serialno) == fTracks.end()) { + bool new_serialno = fTracks.find(serialno) == fTracks.end(); + if (new_serialno) { // this is an unknown serialno if (ogg_page_bos(&page) == 0) { TRACE("OggReader::GetPage: non-bos page with unknown serialno\n"); @@ -140,11 +141,14 @@ retry: } fCookies.push_back(serialno); } - status_t status = fTracks[serialno]->AddPage(fPosition, page); - if (status != B_OK) { - return status; + // this check ensures that we only push the initial pages into OggSeekables. + if (!fSeekable || new_serialno) { + status_t status = fTracks[serialno]->AddPage(fPosition, page); + if (status != B_OK) { + return status; + } + fPosition += page.header_len + page.body_len; } - fPosition += page.header_len + page.body_len; return page.header_len + page.body_len; } @@ -183,6 +187,7 @@ get_seekable(BDataIO * data) return seekable; } + static BFile * get_file(BDataIO * data) { @@ -198,6 +203,7 @@ get_file(BDataIO * data) return file; } + status_t OggReader::Sniff(int32 *streamCount) { @@ -221,6 +227,12 @@ OggReader::Sniff(int32 *streamCount) return bytes; } } + if (fSeekable) { + status_t status = FindLastPages(); + if (status != B_OK) { + return status; + } + } *streamCount = fCookies.size(); return B_OK; } @@ -301,6 +313,81 @@ OggReader::GetNextChunk(void *cookie, } +status_t +OggReader::FindLastPages() +{ + TRACE("OggReader::FindLastPages\n"); + status_t result = B_ERROR; + + const int read_size = 256*256; + + ogg_page page; + ogg_sync_state sync; + ogg_sync_init(&sync); + + off_t right = fSeekable->Seek(0, SEEK_END); + off_t left = right; + // we assume the common case is that the last pages are near the end + uint serial_count = 0; + while (serial_count < fCookies.size()) { + int offset; + while ((offset = ogg_sync_pageseek(&sync, &page)) <= 0) { + left += -offset; + if (offset == 0) { + if (fSeekable->Position() >= right) { + if (left == 0) { + TRACE("OggReader::FindLastPages: couldn't find some stream's page!!!\n"); + goto done; + } + left = max_c(0, left - read_size); + result = fSeekable->Seek(left, SEEK_SET); + if (result < 0) { + goto done; + } + ogg_sync_reset(&sync); + continue; + } + char * buffer = ogg_sync_buffer(&sync, read_size); + ssize_t bytes = fSeekable->Read(buffer, read_size); + if (bytes < 0) { + TRACE("OggReader::FindLastPages: Read: error\n"); + result = bytes; + goto done; + } + if (ogg_sync_wrote(&sync, bytes) != 0) { + TRACE("OggReader::FindLastPages: ogg_sync_wrote failed?: error\n"); + goto done; + } + } + } + off_t current = left; + do { + // found a page at "current" + long serialno = ogg_page_serialno(&page); + OggSeekable * track = dynamic_cast(fTracks[serialno]); + if (track == 0) { + TRACE("OggReader::FindLastPages: unknown serialno == TODO: chaining?\n"); + } else { + if (track->GetLastPagePosition() == 0) { + serial_count++; + } + track->SetLastPagePosition(current); + } + current += page.header_len + page.body_len; + } while ((current < right) && (ogg_sync_pageout(&sync, &page) == 1)); + right = left; + ogg_sync_reset(&sync); + } + result = B_OK; +done: + ogg_sync_clear(&sync); + return result; +} + +/* + * OggReaderPlugin + */ + Reader * OggReaderPlugin::NewReader() { diff --git a/src/add-ons/media/plugins/ogg/OggReaderPlugin.h b/src/add-ons/media/plugins/ogg/OggReaderPlugin.h index b9d095edc9..de87f5c3ce 100644 --- a/src/add-ons/media/plugins/ogg/OggReaderPlugin.h +++ b/src/add-ons/media/plugins/ogg/OggReaderPlugin.h @@ -36,6 +36,8 @@ public: media_header *mediaHeader); private: + status_t FindLastPages(); + ogg_sync_state fSync; BLocker fSyncLock; serialno_OggTrack_map fTracks; diff --git a/src/add-ons/media/plugins/ogg/OggSeekable.cpp b/src/add-ons/media/plugins/ogg/OggSeekable.cpp index 2ecf4425a8..498f9e7cf1 100644 --- a/src/add-ons/media/plugins/ogg/OggSeekable.cpp +++ b/src/add-ons/media/plugins/ogg/OggSeekable.cpp @@ -49,6 +49,8 @@ OggSeekable::OggSeekable(long serialno) TRACE("OggSeekable::OggSeekable\n"); ogg_sync_init(&fSync); ogg_stream_init(&fStreamState,serialno); + fPosition = 0; + fLastPagePosition = 0; } @@ -86,6 +88,56 @@ OggSeekable::AddPage(off_t position, const ogg_page & page) } +void +OggSeekable::SetLastPagePosition(off_t position) +{ + fLastPagePosition = max_c(fLastPagePosition, position); +} + + +off_t +OggSeekable::GetLastPagePosition() +{ + return fLastPagePosition; +} + + +off_t +OggSeekable::Seek(off_t position, int32 mode) +{ + BAutolock autolock(fPositionLock); + off_t result = fPositionIO->Seek(position, mode); + if (result >= 0) { + fPosition = result; + ogg_sync_reset(&fSync); + ogg_stream_reset(&fStreamState); + } + return result; +} + + +off_t +OggSeekable::Position(void) const +{ + return fPosition; +} + + +status_t +OggSeekable::GetSize(off_t * size) +{ + BAutolock autolock(fPositionLock); + off_t prior = fPositionIO->Position(); + off_t result = fPositionIO->Seek(0, SEEK_END); + fPositionIO->Seek(prior, SEEK_SET); + if (result >= 0) { + *size = result; + result = B_OK; + } + return result; +} + + status_t OggSeekable::ReadPage(ogg_page * page, int read_size) { @@ -98,16 +150,16 @@ OggSeekable::ReadPage(ogg_page * page, int read_size) char * buffer = ogg_sync_buffer(&fSync, read_size); ssize_t bytes = fPositionIO->ReadAt(fPosition, buffer, read_size); if (bytes == 0) { - TRACE("OggReader::ReadPageAt: ReadAt: no data\n"); + TRACE("OggSeekable::ReadPage: ReadAt: no data\n"); return B_LAST_BUFFER_ERROR; } if (bytes < 0) { - TRACE("OggReader::ReadPageAt: ReadAt: error\n"); + TRACE("OggSeekable::ReadPage: ReadAt: error\n"); return bytes; } fPosition += bytes; if (ogg_sync_wrote(&fSync, bytes) != 0) { - TRACE("OggReader::ReadPageAt: ogg_sync_wrote failed?: error\n"); + TRACE("OggSeekable::ReadPage: ogg_sync_wrote failed?: error\n"); return B_ERROR; } } @@ -120,25 +172,25 @@ OggSeekable::ReadPage(ogg_page * page, int read_size) char * buffer = ogg_sync_buffer(&fSync, read_size); ssize_t bytes = fPositionIO->ReadAt(fPosition, buffer, read_size); if (bytes == 0) { - TRACE("OggReader::ReadPageAt: ReadAt 2: no data\n"); + TRACE("OggSeekable::ReadPage: ReadAt 2: no data\n"); return B_LAST_BUFFER_ERROR; } if (bytes < 0) { - TRACE("OggReader::ReadPageAt: ReadAt 2: error\n"); + TRACE("OggSeekable::ReadPage: ReadAt 2: error\n"); return bytes; } fPosition += bytes; if (ogg_sync_wrote(&fSync, bytes) != 0) { - TRACE("OggReader::ReadPageAt: ogg_sync_wrote 2 failed?: error\n"); + TRACE("OggSeekable::ReadPage: ogg_sync_wrote 2 failed?: error\n"); return B_ERROR; } } if (result == -1) { - TRACE("OggReader::ReadPageAt: ogg_sync_pageout: not synced??\n"); + TRACE("OggSeekable::ReadPage: ogg_sync_pageout: not synced??\n"); return B_ERROR; } if (ogg_page_version(page) != 0) { - TRACE("OggReader::GetPageAt: ogg_page_version: error in page encoding??\n"); + TRACE("OggSeekable::ReadPage: ogg_page_version: error in page encoding??\n"); #ifdef STRICT_OGG return B_ERROR; #endif @@ -197,6 +249,38 @@ OggSeekable::GetStreamInfo(int64 *frameCount, bigtime_t *duration, } +status_t +OggSeekable::Seek(uint32 seekTo, int64 *frame, bigtime_t *time) +{ + if (seekTo == B_MEDIA_SEEK_TO_FRAME) { + if (*frame != 0) { + return B_UNSUPPORTED; + } + } + if (seekTo == B_MEDIA_SEEK_TO_TIME) { + if (*time != 0) { + return B_UNSUPPORTED; + } + } + off_t result = Seek(0, SEEK_SET); + if (result < 0) { + return result; + } + + for(uint i = 0 ; i < GetHeaderPackets().size() ; i++) { + ogg_packet packet; + status_t status = GetPacket(&packet); + if (status != B_OK) { + return status; + } + } + + *frame = 0; + *time = 0; + return B_OK; +} + + // the default chunk is an ogg packet status_t OggSeekable::GetNextChunk(void **chunkBuffer, int32 *chunkSize, diff --git a/src/add-ons/media/plugins/ogg/OggSeekable.h b/src/add-ons/media/plugins/ogg/OggSeekable.h index eb5bc0bd9a..cd3c83d794 100644 --- a/src/add-ons/media/plugins/ogg/OggSeekable.h +++ b/src/add-ons/media/plugins/ogg/OggSeekable.h @@ -15,6 +15,7 @@ public: // interface for OggReader virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration, media_format *format); + virtual status_t Seek(uint32 seekTo, int64 *frame, bigtime_t *time); virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize, media_header *mediaHeader); @@ -26,7 +27,15 @@ public: // reader push input function status_t AddPage(off_t position, const ogg_page & page); + void SetLastPagePosition(off_t position); + off_t GetLastPagePosition(); +private: + off_t fLastPagePosition; + protected: + off_t Seek(off_t position, int32 mode); + off_t Position(void) const; + status_t GetSize(off_t * size); status_t ReadPage(ogg_page * page, int read_size = 4*B_PAGE_SIZE); // subclass pull input function diff --git a/src/add-ons/media/plugins/ogg/OggSpeexSeekable.cpp b/src/add-ons/media/plugins/ogg/OggSpeexSeekable.cpp index 2c6d455807..58f5eb878c 100644 --- a/src/add-ons/media/plugins/ogg/OggSpeexSeekable.cpp +++ b/src/add-ons/media/plugins/ogg/OggSpeexSeekable.cpp @@ -140,8 +140,53 @@ OggSpeexSeekable::GetStreamInfo(int64 *frameCount, bigtime_t *duration, } format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets())); - *duration = 100000000; - *frameCount = 60000; + + // TODO: count the frames in the first page.. somehow.. :-/ + int64 frames = 0; + + // seek back to the start + int64 frame = 0; + bigtime_t time = 0; + result = Seek(B_MEDIA_SEEK_TO_TIME, &frame, &time); + if (result != B_OK) { + return result; + } + + ogg_page page; + // read the first page + result = ReadPage(&page); + if (result != B_OK) { + return result; + } + int64 first_granulepos = ogg_page_granulepos(&page); + if (first_granulepos < 0) { + // negative start granulepos indicates that we discard that many frames + frames -= first_granulepos; + first_granulepos = 0; + } + + // read our last page + off_t last = Seek(GetLastPagePosition(), SEEK_SET); + if (last < 0) { + return last; + } + result = ReadPage(&page); + if (result != B_OK) { + return result; + } + int64 last_granulepos = ogg_page_granulepos(&page); + + // seek back to the start + result = Seek(B_MEDIA_SEEK_TO_TIME, &frame, &time); + if (result != B_OK) { + return result; + } + + // compute frame count and duration from sample count + frames += last_granulepos - first_granulepos; + *frameCount = frames; + *duration = (1000000LL * frames) / (long long)format->u.encoded_audio.output.frame_rate; + return B_OK; } diff --git a/src/add-ons/media/plugins/ogg/OggTobiasSeekable.cpp b/src/add-ons/media/plugins/ogg/OggTobiasSeekable.cpp index 9d356f5c30..fce6913b02 100644 --- a/src/add-ons/media/plugins/ogg/OggTobiasSeekable.cpp +++ b/src/add-ons/media/plugins/ogg/OggTobiasSeekable.cpp @@ -188,19 +188,16 @@ OggTobiasSeekable::GetStreamInfo(int64 *frameCount, bigtime_t *duration, if (result != B_OK) { return result; } - *frameCount = (bigtime_t)(3 * 3600 * format->u.encoded_video.output.field_rate); } else if (strcmp(header->streamtype, "audio") == 0) { result = get_audio_format(header, format); if (result != B_OK) { return result; } - *frameCount = 2000000; } else if (strcmp(header->streamtype, "text") == 0) { result = get_text_format(header, format); if (result != B_OK) { return result; } - *frameCount = 2000000; } else { *frameCount = 0; // unknown streamtype @@ -219,11 +216,62 @@ OggTobiasSeekable::GetStreamInfo(int64 *frameCount, bigtime_t *duration, format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets())); fMediaFormat = *format; fMicrosecPerFrame = header->time_unit / 10.0; + + // TODO: count the frames in the first page.. somehow.. :-/ + int64 frames = 0; + + ogg_page page; + // read the first page + result = ReadPage(&page); + if (result != B_OK) { + return result; + } + int64 first_granulepos = ogg_page_granulepos(&page); + if (first_granulepos < 0) { + // negative start granulepos indicates that we discard that many frames + frames -= first_granulepos; + first_granulepos = 0; + } + + // read our last page + off_t last = inherited::Seek(GetLastPagePosition(), SEEK_SET); + if (last < 0) { + return last; + } + result = ReadPage(&page); + if (result != B_OK) { + return result; + } + int64 last_granulepos = ogg_page_granulepos(&page); + + // seek back to the start + int64 frame = 0; + bigtime_t time = 0; + result = Seek(B_MEDIA_SEEK_TO_TIME, &frame, &time); + if (result != B_OK) { + return result; + } + + // compute frame count and duration from sample count + frames += last_granulepos - first_granulepos; + *frameCount = frames; *duration = (bigtime_t)(*frameCount * fMicrosecPerFrame); return B_OK; } +status_t +OggTobiasSeekable::Seek(uint32 seekTo, int64 *frame, bigtime_t *time) +{ + status_t status = inherited::Seek(seekTo, frame, time); + if (status == B_OK) { + fCurrentFrame = *frame; + fCurrentTime = *time; + } + return status; +} + + status_t OggTobiasSeekable::GetNextChunk(void **chunkBuffer, int32 *chunkSize, media_header *mediaHeader) diff --git a/src/add-ons/media/plugins/ogg/OggTobiasSeekable.h b/src/add-ons/media/plugins/ogg/OggTobiasSeekable.h index a51e4c07fa..447e0b36dc 100644 --- a/src/add-ons/media/plugins/ogg/OggTobiasSeekable.h +++ b/src/add-ons/media/plugins/ogg/OggTobiasSeekable.h @@ -6,6 +6,8 @@ namespace BPrivate { namespace media { class OggTobiasSeekable : public OggSeekable { +private: + typedef OggSeekable inherited; public: static bool IsValidHeader(const ogg_packet & packet); public: @@ -14,6 +16,7 @@ public: virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration, media_format *format); + virtual status_t Seek(uint32 seekTo, int64 *frame, bigtime_t *time); virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize, media_header *mediaHeader); diff --git a/src/add-ons/media/plugins/ogg/OggVorbisSeekable.cpp b/src/add-ons/media/plugins/ogg/OggVorbisSeekable.cpp index 2323e8738f..bfe3b4bf29 100644 --- a/src/add-ons/media/plugins/ogg/OggVorbisSeekable.cpp +++ b/src/add-ons/media/plugins/ogg/OggVorbisSeekable.cpp @@ -48,6 +48,7 @@ typedef struct vorbis_info{ void *codec_setup; } vorbis_info; + // based on libvorbis/info.c _vorbis_unpack_info static int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb){ vi->version = oggpack_read(opb, 32); @@ -79,6 +80,7 @@ static int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb){ return 0; } + /* * OggVorbisSeekable implementations */ @@ -188,10 +190,45 @@ OggVorbisSeekable::GetStreamInfo(int64 *frameCount, bigtime_t *duration, format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets())); + // TODO: count the frames in the first page.. somehow.. :-/ + int64 frames = 0; + + ogg_page page; + // read the first page + result = ReadPage(&page); + if (result != B_OK) { + return result; + } + int64 first_granulepos = ogg_page_granulepos(&page); + if (first_granulepos < 0) { + // negative start granulepos indicates that we discard that many frames + frames -= first_granulepos; + first_granulepos = 0; + } + + // read our last page + off_t last = Seek(GetLastPagePosition(), SEEK_SET); + if (last < 0) { + return last; + } + result = ReadPage(&page); + if (result != B_OK) { + return result; + } + int64 last_granulepos = ogg_page_granulepos(&page); + + // seek back to the start + int64 frame = 0; + bigtime_t time = 0; + result = Seek(B_MEDIA_SEEK_TO_TIME, &frame, &time); + if (result != B_OK) { + return result; + } + // compute frame count and duration from sample count - int64 samples = 1000000; - *frameCount = samples; - *duration = (1000000LL * samples) / (long long)format->u.encoded_audio.output.frame_rate; + frames += last_granulepos - first_granulepos; + *frameCount = frames; + *duration = (1000000LL * frames) / (long long)format->u.encoded_audio.output.frame_rate; return B_OK; }