/* * Copyright © 2000-2004 Ingo Weinhold * Copyright © 2006-2008 Stephan Aßmus * All rights reserved. Distributed under the terms of the MIT License. */ #include "MediaTrackAudioSupplier.h" #include #include #include #include #include #include using namespace std; //#define TRACE_AUDIO_SUPPLIER #ifdef TRACE_AUDIO_SUPPLIER # define TRACE(x...) printf("MediaTrackAudioSupplier::" x) #else # define TRACE(x...) #endif // #pragma mark - Buffer struct MediaTrackAudioSupplier::Buffer { void* data; int64 offset; int64 size; bigtime_t time_stamp; static int CompareOffset(const void* a, const void* b); }; int MediaTrackAudioSupplier::Buffer::CompareOffset(const void* a, const void* b) { const Buffer* buffer1 = *(const Buffer**)a; const Buffer* buffer2 = *(const Buffer**)b; int result = 0; if (buffer1->offset < buffer2->offset) result = -1; else if (buffer1->offset > buffer2->offset) result = 1; return result; } // #pragma mark - MediaTrackAudioSupplier MediaTrackAudioSupplier::MediaTrackAudioSupplier(BMediaTrack* mediaTrack, int32 trackIndex) : AudioTrackSupplier(), fMediaTrack(mediaTrack), fBuffer(NULL), fBufferOffset(0), fBufferSize(0), fBuffers(10), fHasKeyFrames(false), fCountFrames(0), fReportSeekError(true), fTrackIndex(trackIndex) { _InitFromTrack(); } MediaTrackAudioSupplier::~MediaTrackAudioSupplier() { _FreeBuffers(); delete[] fBuffer; } const media_format& MediaTrackAudioSupplier::Format() const { return AudioReader::Format(); } status_t MediaTrackAudioSupplier::GetEncodedFormat(media_format* format) const { if (!fMediaTrack) return B_NO_INIT; return fMediaTrack->EncodedFormat(format); } status_t MediaTrackAudioSupplier::GetCodecInfo(media_codec_info* info) const { if (!fMediaTrack) return B_NO_INIT; return fMediaTrack->GetCodecInfo(info); } bigtime_t MediaTrackAudioSupplier::Duration() const { if (!fMediaTrack) return 0; return fMediaTrack->Duration(); } // #pragma mark - AudioReader bigtime_t MediaTrackAudioSupplier::InitialLatency() const { // TODO: this is just a wild guess, and not really founded on anything. return 100000; } status_t MediaTrackAudioSupplier::Read(void* buffer, int64 pos, int64 frames) { TRACE("Read(%p, %lld, %lld)\n", buffer, pos, frames); TRACE(" this: %p, fOutOffset: %lld\n", this, fOutOffset); //printf("MediaTrackAudioSupplier::Read(%p, %lld, %lld)\n", buffer, pos, frames); status_t error = InitCheck(); if (error != B_OK) { TRACE("Read() InitCheck failed\n"); return error; } // convert pos according to our offset pos += fOutOffset; // Fill the frames after the end of the track with silence. if (fCountFrames > 0 && pos + frames > fCountFrames) { int64 size = max((int64)0, fCountFrames - pos); ReadSilence(SkipFrames(buffer, size), frames - size); frames = size; } TRACE(" after eliminating the frames after the track end: %p, %lld, %lld\n", buffer, pos, frames); #if 0 const media_format& format = Format(); int64 size = format.u.raw_audio.buffer_size; uint32 bytesPerFrame = format.u.raw_audio.channel_count * (format.u.raw_audio.format & media_raw_audio_format::B_AUDIO_SIZE_MASK); uint32 framesPerBuffer = size / bytesPerFrame; if (fMediaTrack->CurrentFrame() != pos) { printf(" needing to seek: %lld (%lld)\n", pos, fMediaTrack->CurrentFrame()); int64 keyFrame = pos; error = fMediaTrack->FindKeyFrameForFrame(&keyFrame, B_MEDIA_SEEK_CLOSEST_BACKWARD); if (error == B_OK) { error = fMediaTrack->SeekToFrame(&keyFrame, B_MEDIA_SEEK_CLOSEST_BACKWARD); } if (error != B_OK) { printf(" error seeking to position: %lld (%lld)\n", pos, fMediaTrack->CurrentFrame()); return error; } if (keyFrame < pos) { printf(" need to skip %lld frames\n", pos - keyFrame); uint8 dummyBuffer[size]; while (pos - keyFrame >= framesPerBuffer) { printf(" skipped %lu frames (full buffer)\n", framesPerBuffer); int64 sizeToRead = size; fMediaTrack->ReadFrames(dummyBuffer, &sizeToRead); keyFrame += framesPerBuffer; } int64 restSize = pos - keyFrame; if (restSize > 0) { printf(" skipped %lu frames (rest)\n", framesPerBuffer); fMediaTrack->ReadFrames(dummyBuffer, &restSize); } } } while (frames > 0) { printf(" reading %lu frames (full buffer)\n", framesPerBuffer); int64 sizeToRead = min_c(size, frames * bytesPerFrame); fMediaTrack->ReadFrames(buffer, &sizeToRead); buffer = (uint8*)buffer + sizeToRead; frames -= framesPerBuffer; } printf(" done\n\n"); #else // read the cached frames bigtime_t time = system_time(); if (frames > 0) _ReadCachedFrames(buffer, pos, frames, time); TRACE(" after reading from cache: %p, %lld, %lld\n", buffer, pos, frames); // read the remaining (uncached) frames if (frames > 0) _ReadUncachedFrames(buffer, pos, frames, time); #endif TRACE("Read() done\n"); return B_OK; } // InitCheck status_t MediaTrackAudioSupplier::InitCheck() const { status_t error = AudioReader::InitCheck(); if (error == B_OK && (!fMediaTrack || !fBuffer)) error = B_NO_INIT; return error; } // #pragma mark - // _InitFromTrack void MediaTrackAudioSupplier::_InitFromTrack() { TRACE("_InitFromTrack()\n"); // Try to suggest a big buffer size, we do a lot of caching... fFormat.u.raw_audio.buffer_size = 16384; if (fMediaTrack == NULL || fMediaTrack->DecodedFormat(&fFormat) != B_OK || fFormat.type != B_MEDIA_RAW_AUDIO) { fMediaTrack = NULL; return; } #ifdef TRACE_AUDIO_SUPPLIER char formatString[256]; string_for_format(fFormat, formatString, 256); TRACE("_InitFromTrack(): format is: %s\n", formatString); TRACE("_InitFromTrack(): buffer size: %ld\n", fFormat.u.raw_audio.buffer_size); #endif fBuffer = new (nothrow) char[fFormat.u.raw_audio.buffer_size]; _AllocateBuffers(); // Find out, if the track has key frames: as a heuristic we // check, if the first and the second frame have the same backward // key frame. // Note: It shouldn't harm that much, if we're wrong and the // track has key frame although we found out that it has not. #if 0 int64 keyFrame0 = 0; int64 keyFrame1 = 1; fMediaTrack->FindKeyFrameForFrame(&keyFrame0, B_MEDIA_SEEK_CLOSEST_BACKWARD); fMediaTrack->FindKeyFrameForFrame(&keyFrame1, B_MEDIA_SEEK_CLOSEST_BACKWARD); fHasKeyFrames = (keyFrame0 == keyFrame1); #else fHasKeyFrames = true; #endif // get the length of the track fCountFrames = fMediaTrack->CountFrames(); TRACE("_InitFromTrack(): keyframes: %" B_PRId16 ", frame count: %" B_PRId64 "\n", fHasKeyFrames, fCountFrames); printf("_InitFromTrack(): keyframes: %" B_PRId16 ", frame count: %" B_PRId64 "\n", fHasKeyFrames, fCountFrames); } // _FramesPerBuffer int64 MediaTrackAudioSupplier::_FramesPerBuffer() const { int64 sampleSize = fFormat.u.raw_audio.format & media_raw_audio_format::B_AUDIO_SIZE_MASK; int64 frameSize = sampleSize * fFormat.u.raw_audio.channel_count; return fFormat.u.raw_audio.buffer_size / frameSize; } // _CopyFrames // // Given two buffers starting at different frame offsets, this function // copies /frames/ frames at position /position/ from the source to the // target buffer. // Note that no range checking is done. void MediaTrackAudioSupplier::_CopyFrames(void* source, int64 sourceOffset, void* target, int64 targetOffset, int64 position, int64 frames) const { int64 sampleSize = fFormat.u.raw_audio.format & media_raw_audio_format::B_AUDIO_SIZE_MASK; int64 frameSize = sampleSize * fFormat.u.raw_audio.channel_count; source = (char*)source + frameSize * (position - sourceOffset); target = (char*)target + frameSize * (position - targetOffset); memcpy(target, source, frames * frameSize); } // _CopyFrames // // Given two buffers starting at different frame offsets, this function // copies /frames/ frames at position /position/ from the source to the // target buffer. This version expects a cache buffer as source. // Note that no range checking is done. void MediaTrackAudioSupplier::_CopyFrames(Buffer* buffer, void* target, int64 targetOffset, int64 position, int64 frames) const { _CopyFrames(buffer->data, buffer->offset, target, targetOffset, position, frames); } // _AllocateBuffers // // Allocates a set of buffers. void MediaTrackAudioSupplier::_AllocateBuffers() { int32 count = 10; _FreeBuffers(); int32 bufferSize = fFormat.u.raw_audio.buffer_size; char* data = new (nothrow) char[bufferSize * count]; for (; count > 0; count--) { Buffer* buffer = new (nothrow) Buffer; if (!buffer || !fBuffers.AddItem(buffer)) { delete buffer; if (fBuffers.CountItems() == 0) delete[] data; return; } buffer->data = data; data += bufferSize; buffer->offset = 0; buffer->size = 0; buffer->time_stamp = 0; } } // _FreeBuffers // // Frees the allocated buffers. void MediaTrackAudioSupplier::_FreeBuffers() { if (fBuffers.CountItems() > 0) { delete[] (char*)_BufferAt(0)->data; for (int32 i = 0; Buffer* buffer = _BufferAt(i); i++) delete buffer; fBuffers.MakeEmpty(); } } // _BufferAt // // Returns the buffer at index /index/. MediaTrackAudioSupplier::Buffer* MediaTrackAudioSupplier::_BufferAt(int32 index) const { return (Buffer*)fBuffers.ItemAt(index); } // _FindBufferAtFrame // // If any buffer starts at offset /frame/, it is returned, NULL otherwise. MediaTrackAudioSupplier::Buffer* MediaTrackAudioSupplier::_FindBufferAtFrame(int64 frame) const { Buffer* buffer = NULL; for (int32 i = 0; ((buffer = _BufferAt(i))) && buffer->offset != frame; i++); return buffer; } // _FindUnusedBuffer // // Returns the first unused buffer or NULL if all buffers are used. MediaTrackAudioSupplier::Buffer* MediaTrackAudioSupplier::_FindUnusedBuffer() const { Buffer* buffer = NULL; for (int32 i = 0; ((buffer = _BufferAt(i))) && buffer->size != 0; i++) ; return buffer; } // _FindUsableBuffer // // Returns either an unused buffer or, if all buffers are used, the least // recently used buffer. // In every case a buffer is returned. MediaTrackAudioSupplier::Buffer* MediaTrackAudioSupplier::_FindUsableBuffer() const { Buffer* result = _FindUnusedBuffer(); if (!result) { // find the least recently used buffer. result = _BufferAt(0); for (int32 i = 1; Buffer* buffer = _BufferAt(i); i++) { if (buffer->time_stamp < result->time_stamp) result = buffer; } } return result; } // _FindUsableBufferFor // // In case there already exists a buffer that starts at position this // one is returned. Otherwise the function returns either an unused // buffer or, if all buffers are used, the least recently used buffer. // In every case a buffer is returned. MediaTrackAudioSupplier::Buffer* MediaTrackAudioSupplier::_FindUsableBufferFor(int64 position) const { Buffer* buffer = _FindBufferAtFrame(position); if (buffer == NULL) buffer = _FindUsableBuffer(); return buffer; } // _GetBuffersFor // // Adds pointers to all buffers to the list that contain data of the // supplied interval. void MediaTrackAudioSupplier::_GetBuffersFor(BList& buffers, int64 position, int64 frames) const { buffers.MakeEmpty(); for (int32 i = 0; Buffer* buffer = _BufferAt(i); i++) { // Calculate the intersecting interval and add the buffer if it is // not empty. int32 startFrame = max(position, buffer->offset); int32 endFrame = min(position + frames, buffer->offset + buffer->size); if (startFrame < endFrame) buffers.AddItem(buffer); } } // _TouchBuffer // // Sets a buffer's time stamp to the current system time. void MediaTrackAudioSupplier::_TouchBuffer(Buffer* buffer) { buffer->time_stamp = system_time(); } // _ReadBuffer // // Read a buffer from the current position (which is supplied in /position/) // into /buffer/. The buffer's time stamp is set to the current system time. status_t MediaTrackAudioSupplier::_ReadBuffer(Buffer* buffer, int64 position) { return _ReadBuffer(buffer, position, system_time()); } // _ReadBuffer // // Read a buffer from the current position (which is supplied in /position/) // into /buffer/. The buffer's time stamp is set to the supplied time. status_t MediaTrackAudioSupplier::_ReadBuffer(Buffer* buffer, int64 position, bigtime_t time) { status_t error = fMediaTrack->ReadFrames(buffer->data, &buffer->size); TRACE("_ReadBuffer(%p, %lld): %s\n", buffer->data, buffer->size, strerror(error)); buffer->offset = position; buffer->time_stamp = time; if (error != B_OK) buffer->size = 0; return error; } // _ReadCachedFrames // // Tries to read as much as possible data from the cache. The supplied // buffer pointer as well as position and number of frames are adjusted // accordingly. The used cache buffers are stamped with the supplied // time. void MediaTrackAudioSupplier::_ReadCachedFrames(void*& dest, int64& pos, int64& frames, bigtime_t time) { // Get a list of all cache buffers that contain data of the interval, // and sort it. BList buffers(10); _GetBuffersFor(buffers, pos, frames); buffers.SortItems(Buffer::CompareOffset); // Step forward through the list of cache buffers and try to read as // much data from the beginning as possible. for (int32 i = 0; Buffer* buffer = (Buffer*)buffers.ItemAt(i); i++) { if (buffer->offset <= pos && buffer->offset + buffer->size > pos) { // read from the beginning int64 size = min(frames, buffer->offset + buffer->size - pos); _CopyFrames(buffer->data, buffer->offset, dest, pos, pos, size); pos += size; frames -= size; dest = SkipFrames(dest, size); buffer->time_stamp = time; } } // Step backward through the list of cache buffers and try to read as // much data from the end as possible. for (int32 i = buffers.CountItems() - 1; Buffer* buffer = (Buffer*)buffers.ItemAt(i); i++) { if (buffer->offset < pos + frames && buffer->offset + buffer->size >= pos + frames) { // read from the end int64 size = min(frames, pos + frames - buffer->offset); _CopyFrames(buffer->data, buffer->offset, dest, pos, pos + frames - size, size); frames -= size; buffer->time_stamp = time; } } } /*! Reads /frames/ frames from /position/ into /buffer/. The frames are not read from the cache, but read frames are cached, if possible. New cache buffers are stamped with the supplied time. If an error occurs, the untouched part of the buffer is set to 0. */ status_t MediaTrackAudioSupplier::_ReadUncachedFrames(void* buffer, int64 position, int64 frames, bigtime_t time) { TRACE("_ReadUncachedFrames()\n"); status_t error = B_OK; // seek to the position int64 currentPos = position; if (frames > 0) { error = _SeekToKeyFrameBackward(currentPos); TRACE("_ReadUncachedFrames() - seeked to position: %lld\n", currentPos); // if (position - currentPos > 100000) // printf("MediaTrackAudioSupplier::_ReadUncachedFrames() - " // "keyframe was far away: %lld -> %lld\n", position, currentPos); } // read the frames // TODO: Calculate timeout, 0.25 times duration of "frames" seems good. bigtime_t timeout = 10000; while (error == B_OK && frames > 0) { Buffer* cacheBuffer = _FindUsableBufferFor(currentPos); TRACE("_ReadUncachedFrames() - usable buffer found: %p, " "position: %lld/%lld\n", cacheBuffer, currentPos, position); error = _ReadBuffer(cacheBuffer, currentPos, time); if (error == B_OK) { int64 size = min(position + frames, cacheBuffer->offset + cacheBuffer->size) - position; if (size > 0) { _CopyFrames(cacheBuffer, buffer, position, position, size); buffer = SkipFrames(buffer, size); position += size; frames -= size; } currentPos += cacheBuffer->size; } if (system_time() - time > timeout) { error = B_TIMED_OUT; break; } } #if 0 // Ensure that all frames up to the next key frame are cached. // This avoids, that each read reaches the BMediaTrack. if (error == B_OK) { int64 nextKeyFrame = currentPos; if (_FindKeyFrameForward(nextKeyFrame) == B_OK) { while (currentPos < nextKeyFrame) { // Check, if data at this position are cache. // If not read it. Buffer* cacheBuffer = _FindBufferAtFrame(currentPos); if (!cacheBuffer || cacheBuffer->size == 0) { cacheBuffer = _FindUsableBufferFor(currentPos); if (_ReadBuffer(cacheBuffer, currentPos, time) != B_OK) break; } if (cacheBuffer) currentPos += cacheBuffer->size; } } } #endif // on error fill up the buffer with silence if (error != B_OK && frames > 0) ReadSilence(buffer, frames); return error; } // _FindKeyFrameForward status_t MediaTrackAudioSupplier::_FindKeyFrameForward(int64& position) { status_t error = B_OK; if (fHasKeyFrames) { error = fMediaTrack->FindKeyFrameForFrame( &position, B_MEDIA_SEEK_CLOSEST_FORWARD); } else { int64 framesPerBuffer = _FramesPerBuffer(); position += framesPerBuffer - 1; position = position % framesPerBuffer; } return error; } // _FindKeyFrameBackward status_t MediaTrackAudioSupplier::_FindKeyFrameBackward(int64& position) { status_t error = B_OK; if (fHasKeyFrames) { error = fMediaTrack->FindKeyFrameForFrame( &position, B_MEDIA_SEEK_CLOSEST_BACKWARD); } else position -= position % _FramesPerBuffer(); return error; } #if 0 // _SeekToKeyFrameForward status_t MediaTrackAudioSupplier::_SeekToKeyFrameForward(int64& position) { if (position == fMediaTrack->CurrentFrame()) return B_OK; status_t error = B_OK; if (fHasKeyFrames) { #ifdef TRACE_AUDIO_SUPPLIER int64 oldPosition = position; #endif error = fMediaTrack->SeekToFrame(&position, B_MEDIA_SEEK_CLOSEST_FORWARD); TRACE("_SeekToKeyFrameForward() - seek to key frame forward: " "%lld -> %lld (%lld)\n", oldPosition, position, fMediaTrack->CurrentFrame()); } else { _FindKeyFrameForward(position); error = fMediaTrack->SeekToFrame(&position); } return error; } #endif // _SeekToKeyFrameBackward status_t MediaTrackAudioSupplier::_SeekToKeyFrameBackward(int64& position) { int64 currentPosition = fMediaTrack->CurrentFrame(); if (position == currentPosition) return B_OK; status_t error = B_OK; if (fHasKeyFrames) { int64 wantedPosition = position; error = fMediaTrack->FindKeyFrameForFrame(&position, B_MEDIA_SEEK_CLOSEST_BACKWARD); if (error == B_OK && currentPosition > position && currentPosition < wantedPosition) { // The current position is before the wanted position, // but later than the keyframe, so seeking is worse. position = currentPosition; return B_OK; } if (error == B_OK && position > wantedPosition) { // We asked to seek backwards, but the extractor seeked // forwards! Returning an error here will cause silence // to be produced. return B_ERROR; } if (error == B_OK) error = fMediaTrack->SeekToFrame(&position, 0); if (error != B_OK) { position = fMediaTrack->CurrentFrame(); // if (fReportSeekError) { printf(" seek to key frame backward: %" B_PRId64 " -> %" B_PRId64 " (%" B_PRId64 ") - %s\n", wantedPosition, position, fMediaTrack->CurrentFrame(), strerror(error)); fReportSeekError = false; // } } else { fReportSeekError = true; } } else { _FindKeyFrameBackward(position); error = fMediaTrack->SeekToFrame(&position); } return error; }