First cut at improving avi seeking
git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@29308 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -65,6 +65,13 @@ struct avi_cookie
|
||||
|
||||
// video only:
|
||||
uint32 line_count;
|
||||
|
||||
// audio only:
|
||||
uint32 sample_size;
|
||||
uint32 frame_size;
|
||||
int64 byte_pos;
|
||||
uint16 bytes_per_second;
|
||||
bool is_vbr;
|
||||
};
|
||||
|
||||
|
||||
@@ -150,6 +157,9 @@ aviReader::AllocateCookie(int32 streamNumber, void **_cookie)
|
||||
cookie->buffer_size = 0;
|
||||
cookie->is_audio = false;
|
||||
cookie->is_video = false;
|
||||
cookie->byte_pos = 0;
|
||||
cookie->is_vbr = false;
|
||||
cookie->bytes_per_second = 0;
|
||||
|
||||
BMediaFormats formats;
|
||||
media_format *format = &cookie->format;
|
||||
@@ -182,6 +192,9 @@ aviReader::AllocateCookie(int32 streamNumber, void **_cookie)
|
||||
|
||||
TRACE("audio frame_count %Ld, duration %.6f\n", cookie->frame_count, cookie->duration / 1E6);
|
||||
|
||||
cookie->bytes_per_second = audio_format->avg_bytes_per_sec;
|
||||
cookie->sample_size = stream_header->sample_size == 0 ? audio_format->bits_per_sample / 8 * audio_format->channels : stream_header->sample_size;
|
||||
|
||||
if (audio_format->format_tag == 0x0001) {
|
||||
// a raw PCM format
|
||||
description.family = B_BEOS_FORMAT_FAMILY;
|
||||
@@ -205,6 +218,7 @@ aviReader::AllocateCookie(int32 streamNumber, void **_cookie)
|
||||
format->u.raw_audio.format |= B_AUDIO_FORMAT_CHANNEL_ORDER_WAVE;
|
||||
format->u.raw_audio.byte_order = B_MEDIA_LITTLE_ENDIAN;
|
||||
format->u.raw_audio.buffer_size = stream_header->suggested_buffer_size;
|
||||
cookie->frame_size = cookie->sample_size;
|
||||
} else {
|
||||
// some encoded format
|
||||
description.family = B_WAV_FORMAT_FAMILY;
|
||||
@@ -214,11 +228,18 @@ aviReader::AllocateCookie(int32 streamNumber, void **_cookie)
|
||||
format->u.encoded_audio.bit_rate = 8 * audio_format->avg_bytes_per_sec;
|
||||
format->u.encoded_audio.output.frame_rate = audio_format->frames_per_sec;
|
||||
format->u.encoded_audio.output.channel_count = audio_format->channels;
|
||||
TRACE("audio: bit_rate %.3f, frame_rate %.1f, channel_count %lu\n",
|
||||
cookie->frame_size = audio_format->block_align == 0 ? 1 : audio_format->block_align;
|
||||
|
||||
// detect vbr audio in avi hack
|
||||
cookie->is_vbr = cookie->frame_size >= 960;
|
||||
|
||||
TRACE("audio: bit_rate %.3f, frame_rate %.1f, channel_count %lu, frame_size %ld, is vbr %s\n",
|
||||
format->u.encoded_audio.bit_rate,
|
||||
format->u.encoded_audio.output.frame_rate,
|
||||
format->u.encoded_audio.output.channel_count);
|
||||
format->u.encoded_audio.output.channel_count,
|
||||
cookie->frame_size, cookie->is_vbr ? "true" : "false");
|
||||
}
|
||||
|
||||
// TODO: this doesn't seem to work (it's not even a fourcc)
|
||||
format->user_data_type = B_CODEC_TYPE_INFO;
|
||||
*(uint32 *)format->user_data = audio_format->format_tag; format->user_data[4] = 0;
|
||||
@@ -252,6 +273,7 @@ aviReader::AllocateCookie(int32 streamNumber, void **_cookie)
|
||||
cookie->frames_per_sec_rate = fFile->StreamInfo(streamNumber)->frames_per_sec_rate;
|
||||
cookie->frames_per_sec_scale = fFile->StreamInfo(streamNumber)->frames_per_sec_scale;
|
||||
cookie->line_count = fFile->AviMainHeader()->height;
|
||||
cookie->frame_size = 1;
|
||||
|
||||
TRACE("video frame_count %Ld, duration %.6f\n", cookie->frame_count,
|
||||
cookie->duration / 1E6);
|
||||
@@ -338,9 +360,10 @@ aviReader::GetStreamInfo(void *_cookie, int64 *frameCount, bigtime_t *duration,
|
||||
status_t
|
||||
aviReader::Seek(void *_cookie, uint32 seekTo, int64 *frame, bigtime_t *time)
|
||||
{
|
||||
// Seek changes the position of the stream
|
||||
avi_cookie *cookie = (avi_cookie *)_cookie;
|
||||
|
||||
TRACE("aviReader::Seek: stream %d, seekTo%s%s%s%s, time %Ld, frame %Ld\n",
|
||||
TRACE("aviReader::Seek: stream %d, seekTo%s%s%s%s, time %.6f, frame %Ld\n",
|
||||
cookie->stream,
|
||||
(seekTo & B_MEDIA_SEEK_TO_TIME) ? " B_MEDIA_SEEK_TO_TIME" : "",
|
||||
(seekTo & B_MEDIA_SEEK_TO_FRAME) ? " B_MEDIA_SEEK_TO_FRAME" : "",
|
||||
@@ -348,25 +371,29 @@ aviReader::Seek(void *_cookie, uint32 seekTo, int64 *frame, bigtime_t *time)
|
||||
" B_MEDIA_SEEK_CLOSEST_FORWARD" : "",
|
||||
(seekTo & B_MEDIA_SEEK_CLOSEST_BACKWARD) ?
|
||||
" B_MEDIA_SEEK_CLOSEST_BACKWARD" : "",
|
||||
*time, *frame);
|
||||
*time / 1000000.0, *frame);
|
||||
|
||||
status_t rv = fFile->Seek(cookie->stream, seekTo, frame, time, false);
|
||||
if (rv == B_OK) {
|
||||
cookie->frame_pos = *frame;
|
||||
TRACE("aviReader::Seek: stream %d, success, setting frame_pos "
|
||||
"to %lld\n", cookie->stream, cookie->frame_pos);
|
||||
if (cookie->is_audio && !cookie->is_vbr) {
|
||||
// calculate byte_pos from time
|
||||
cookie->byte_pos = *time * cookie->bytes_per_second / 1000000LL;
|
||||
}
|
||||
TRACE("aviReader::Seek: stream %d, success, frame_pos = %Ld, time = %.6f\n", cookie->stream, cookie->frame_pos, *time / 1000000.0);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
aviReader::FindKeyFrame(void *_cookie, uint32 flags, int64 *frame,
|
||||
bigtime_t *time)
|
||||
aviReader::FindKeyFrame(void *_cookie, uint32 flags, int64 *frame, bigtime_t *time)
|
||||
{
|
||||
// FindKeyFrame does not change the position of the stream
|
||||
avi_cookie *cookie = (avi_cookie *)_cookie;
|
||||
|
||||
TRACE("aviReader::FindKeyFrame: stream %d, flags%s%s%s%s, time %Ld, "
|
||||
TRACE("aviReader::FindKeyFrame: stream %d, flags%s%s%s%s, time %.6f, "
|
||||
"frame %Ld\n",
|
||||
cookie->stream,
|
||||
(flags & B_MEDIA_SEEK_TO_TIME) ? " B_MEDIA_SEEK_TO_TIME" : "",
|
||||
@@ -375,12 +402,11 @@ aviReader::FindKeyFrame(void *_cookie, uint32 flags, int64 *frame,
|
||||
" B_MEDIA_SEEK_CLOSEST_FORWARD" : "",
|
||||
(flags & B_MEDIA_SEEK_CLOSEST_BACKWARD) ?
|
||||
" B_MEDIA_SEEK_CLOSEST_BACKWARD" : "",
|
||||
*time, *frame);
|
||||
*time / 1000000.0, *frame);
|
||||
|
||||
status_t rv = fFile->Seek(cookie->stream, flags, frame, time, true);
|
||||
if (rv == B_OK) {
|
||||
TRACE("aviReader::FindKeyFrame: stream %d, success\n",
|
||||
cookie->stream);
|
||||
TRACE("aviReader::FindKeyFrame: stream %d, success\n", cookie->stream);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -393,6 +419,7 @@ aviReader::GetNextChunk(void *_cookie, const void **chunkBuffer,
|
||||
avi_cookie *cookie = (avi_cookie *)_cookie;
|
||||
|
||||
int64 start; uint32 size; bool keyframe;
|
||||
|
||||
if (fFile->GetNextChunkInfo(cookie->stream, &start, &size,
|
||||
&keyframe) < B_OK)
|
||||
return B_LAST_BUFFER_ERROR;
|
||||
@@ -412,30 +439,41 @@ aviReader::GetNextChunk(void *_cookie, const void **chunkBuffer,
|
||||
}
|
||||
}
|
||||
|
||||
mediaHeader->start_time = (cookie->frame_pos * 1000000
|
||||
mediaHeader->start_time = (cookie->frame_pos * 1000000LL
|
||||
* cookie->frames_per_sec_scale) / cookie->frames_per_sec_rate;
|
||||
|
||||
TRACE("stream %d (%s): start_time %.6f, pos %.3f %%, frame %Ld chunk size %ld\n",
|
||||
cookie->stream, cookie->is_audio ? "A" : cookie->is_video ? "V" : "?",
|
||||
mediaHeader->start_time / 1000000.0, cookie->frame_pos * 100.0
|
||||
/ cookie->frame_count, cookie->frame_pos, size);
|
||||
|
||||
if (cookie->is_audio) {
|
||||
mediaHeader->type = B_MEDIA_ENCODED_AUDIO;
|
||||
mediaHeader->u.encoded_audio.buffer_flags = keyframe ?
|
||||
B_MEDIA_KEY_FRAME : 0;
|
||||
cookie->frame_pos += size;
|
||||
|
||||
cookie->frame_pos += (uint64)(ceil((double)size / (double)cookie->frame_size)) * cookie->frames_per_sec_scale;
|
||||
cookie->byte_pos += size;
|
||||
// frame_pos is sample no for vbr encoded audio and byte position for everything else
|
||||
// if (cookie->is_vbr) {
|
||||
// advance by frame_size
|
||||
// } else {
|
||||
// cookie->frame_pos += (uint64)(ceil((double)size / (double)cookie->frame_size)) * cookie->frames_per_sec / cookie->avg_bytes_per_sec;
|
||||
// advance by bytes in chunk and calculate frame_pos
|
||||
// time = cookie->byte_pos * 1000000LL / cookie->bytes_per_second;
|
||||
// cookie->frame_pos = time * cookie->frames_per_sec_rate / cookie->frames_per_sec_scale / 1000000LL;
|
||||
// }
|
||||
} else if (cookie->is_video) {
|
||||
mediaHeader->type = B_MEDIA_ENCODED_VIDEO;
|
||||
mediaHeader->u.encoded_video.field_flags = keyframe ?
|
||||
B_MEDIA_KEY_FRAME : 0;
|
||||
mediaHeader->u.encoded_video.first_active_line = 0;
|
||||
mediaHeader->u.encoded_video.line_count = cookie->line_count;
|
||||
cookie->frame_pos += 1;
|
||||
cookie->frame_pos += cookie->frame_size;
|
||||
} else {
|
||||
return B_BAD_VALUE;
|
||||
}
|
||||
|
||||
// TRACE("stream %d (%s): start_time %.6f, pos %.3f %%\n",
|
||||
// cookie->stream, cookie->is_audio ? "A" : cookie->is_video ? "V" : "?",
|
||||
// mediaHeader->start_time / 1000000.0, cookie->frame_pos * 100.0
|
||||
// / cookie->frame_count);
|
||||
|
||||
*chunkBuffer = cookie->buffer;
|
||||
*chunkSize = size;
|
||||
return (int)size == fFile->Source()->ReadAt(start, cookie->buffer, size) ?
|
||||
|
||||
@@ -23,9 +23,24 @@
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include <SupportDefs.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include "FallbackIndex.h"
|
||||
#include "OpenDMLParser.h"
|
||||
|
||||
//#define TRACE_START_INDEX
|
||||
#ifdef TRACE_START_INDEX
|
||||
#define TRACE printf
|
||||
#else
|
||||
#define TRACE(a...)
|
||||
#endif
|
||||
|
||||
#define ERROR(a...) fprintf(stderr, a)
|
||||
|
||||
struct chunk {
|
||||
uint32 chunk_id;
|
||||
uint32 size;
|
||||
};
|
||||
|
||||
FallbackIndex::FallbackIndex(BPositionIO *source, OpenDMLParser *parser)
|
||||
: Index(source, parser)
|
||||
@@ -41,22 +56,101 @@ FallbackIndex::~FallbackIndex()
|
||||
status_t
|
||||
FallbackIndex::Init()
|
||||
{
|
||||
return B_ERROR;
|
||||
// Attempt to build an index by parsing the movi chunk
|
||||
|
||||
bool end_of_movi = false;
|
||||
chunk aChunk;
|
||||
|
||||
TRACE("Building Fallback index\n");
|
||||
|
||||
int stream_index;
|
||||
off_t position;
|
||||
uint32 size;
|
||||
uint64 frame[fStreamCount];
|
||||
uint64 frame_no;
|
||||
bigtime_t pts = 0;
|
||||
bool keyframe = false;
|
||||
uint32 sample_size;
|
||||
uint64 entries = 0;
|
||||
|
||||
const OpenDMLStream *stream;
|
||||
|
||||
for (uint32 i=0;i < fStreamCount; i++) {
|
||||
frame[i] = 0;
|
||||
}
|
||||
|
||||
position = fParser->MovieListStart();
|
||||
|
||||
while (end_of_movi == false) {
|
||||
|
||||
if ((int32)8 != fSource->ReadAt(position, &aChunk, 8)) {
|
||||
ERROR("libOpenDML: FallbackIndex::Init file reading failed\n");
|
||||
return B_IO_ERROR;
|
||||
}
|
||||
|
||||
position += 8;
|
||||
|
||||
stream_index = ((aChunk.chunk_id & 0xff) - '0') * 10;
|
||||
stream_index += ((aChunk.chunk_id >> 8) & 0xff) - '0';
|
||||
|
||||
if ((stream_index < 0) || (stream_index >= fStreamCount)) {
|
||||
if (entries == 0) {
|
||||
ERROR("libOpenDML: FallbackIndex::Init - Failed to build an index, file is too corrupt\n");
|
||||
return B_IO_ERROR;
|
||||
} else {
|
||||
ERROR("libOpenDML: FallbackIndex::Init - Error while trying to build index, file is corrupt but will continue after creating %Ld entries in index\n",entries);
|
||||
return B_OK;
|
||||
}
|
||||
}
|
||||
|
||||
entries++;
|
||||
|
||||
stream = fParser->StreamInfo(stream_index);
|
||||
|
||||
size = aChunk.size;
|
||||
frame_no = frame[stream_index];
|
||||
|
||||
if (stream->is_video) {
|
||||
// Video is easy enough, it is always 1 frame = 1 index
|
||||
pts = frame[stream_index] * 1000000LL * stream->frames_per_sec_scale / stream->frames_per_sec_rate;
|
||||
frame[stream_index]++;
|
||||
} else if (stream->is_audio) {
|
||||
pts = frame[stream_index] * 1000000LL / stream->audio_format->frames_per_sec;
|
||||
|
||||
// Audio varies based on many different hacks over the years
|
||||
// The simplest is chunk size / sample size = no of samples in the chunk for uncompressed audio
|
||||
// ABR Compressed audio is more difficult and VBR Compressed audio is even harder
|
||||
// What follows is what I have worked out from various sources across the internet.
|
||||
if (stream->audio_format->format_tag != 0x0001) {
|
||||
// VBR audio is detected as having a block_align >= 960
|
||||
if (stream->audio_format->block_align >= 960) {
|
||||
// VBR Audio so block_align is the largest no of samples in a chunk
|
||||
// scale is the no of samples in a frame
|
||||
// rate is the sample rate
|
||||
// but we must round up when calculating no of frames in a chunk
|
||||
frame[stream_index] += (uint64)(ceil((double)size / (double)stream->audio_format->block_align)) * stream->frames_per_sec_scale;
|
||||
} else {
|
||||
// ABR Audio so use Chunk Size and avergae bytes per second to determine how many samples there are in the chunk
|
||||
frame[stream_index] += (uint64)(ceil((double)size / (double)stream->audio_format->block_align)) * stream->audio_format->frames_per_sec / stream->audio_format->avg_bytes_per_sec;
|
||||
}
|
||||
} else {
|
||||
// sample size can be corrupt
|
||||
if (stream->stream_header.sample_size > 0) {
|
||||
sample_size = stream->stream_header.sample_size;
|
||||
} else {
|
||||
// compute sample size
|
||||
sample_size = stream->audio_format->bits_per_sample * stream->audio_format->channels / 8;
|
||||
}
|
||||
frame[stream_index] += size / stream->stream_header.sample_size;
|
||||
}
|
||||
}
|
||||
|
||||
AddIndex(stream_index, position, size, frame_no, pts, keyframe);
|
||||
|
||||
position += aChunk.size;
|
||||
|
||||
end_of_movi = position >= fParser->MovieListSize();
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
FallbackIndex::GetNextChunkInfo(int stream_index, int64 *start, uint32 *size,
|
||||
bool *keyframe)
|
||||
{
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
FallbackIndex::Seek(int stream_index, uint32 seekTo, int64 *frame,
|
||||
bigtime_t *time, bool readOnly)
|
||||
{
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,15 +24,182 @@
|
||||
*/
|
||||
#include <SupportDefs.h>
|
||||
#include <stdio.h>
|
||||
#include "ReaderPlugin.h" // B_MEDIA_*
|
||||
#include "Index.h"
|
||||
#include "OpenDMLParser.h"
|
||||
|
||||
//#define TRACE_INDEX
|
||||
#ifdef TRACE_INDEX
|
||||
#define TRACE printf
|
||||
#else
|
||||
#define TRACE(a...)
|
||||
#endif
|
||||
|
||||
#define ERROR(a...) fprintf(stderr, a)
|
||||
|
||||
Index::Index(BPositionIO *source, OpenDMLParser *parser)
|
||||
: fSource(source)
|
||||
, fParser(parser)
|
||||
{
|
||||
fStreamCount = parser->StreamCount();
|
||||
fStreamData.resize(fStreamCount);
|
||||
}
|
||||
|
||||
|
||||
Index::~Index()
|
||||
{
|
||||
fStreamData.clear();
|
||||
}
|
||||
|
||||
status_t
|
||||
Index::GetNextChunkInfo(int stream_index, off_t *start,
|
||||
uint32 *size, bool *keyframe) {
|
||||
MediaStream *data = &fStreamData[stream_index];
|
||||
|
||||
if (data->current_chunk < data->seek_index_next) {
|
||||
*keyframe = data->seek_index[data->current_chunk].keyframe;
|
||||
// skip 8 bytes (chunk id + chunk size)
|
||||
*start = data->seek_index[data->current_chunk].position + 8;
|
||||
*size = data->seek_index[data->current_chunk].size;
|
||||
data->current_chunk++;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
data->current_chunk = 0;
|
||||
|
||||
return B_LAST_BUFFER_ERROR; // should this be end of chunk?
|
||||
}
|
||||
|
||||
status_t
|
||||
Index::Seek(int stream_index, uint32 seekTo, int64 *frame,
|
||||
bigtime_t *time, bool readOnly) {
|
||||
TRACE("Index::Seek: stream %d, seekTo%s%s%s%s, time %Ld, "
|
||||
"frame %Ld\n", stream_index,
|
||||
(seekTo & B_MEDIA_SEEK_TO_TIME) ? " B_MEDIA_SEEK_TO_TIME" : "",
|
||||
(seekTo & B_MEDIA_SEEK_TO_FRAME) ? " B_MEDIA_SEEK_TO_FRAME" : "",
|
||||
(seekTo & B_MEDIA_SEEK_CLOSEST_FORWARD) ?
|
||||
" B_MEDIA_SEEK_CLOSEST_FORWARD" : "",
|
||||
(seekTo & B_MEDIA_SEEK_CLOSEST_BACKWARD) ?
|
||||
" B_MEDIA_SEEK_CLOSEST_BACKWARD" : "",
|
||||
*time, *frame);
|
||||
|
||||
const OpenDMLStream *stream = fParser->StreamInfo(stream_index);
|
||||
MediaStream *data = &fStreamData[stream_index];
|
||||
|
||||
int64 frame_pos;
|
||||
if (seekTo & B_MEDIA_SEEK_TO_FRAME)
|
||||
frame_pos = *frame;
|
||||
else if (seekTo & B_MEDIA_SEEK_TO_TIME) {
|
||||
frame_pos = (*time * stream->frames_per_sec_rate)
|
||||
/ (1000000LL * stream->frames_per_sec_scale);
|
||||
} else
|
||||
return B_BAD_VALUE;
|
||||
|
||||
if (stream->is_audio) {
|
||||
// frame_pos is sample no for audio
|
||||
// Scan index for the chunk that contains the sample no asked for
|
||||
for (uint32 i = 1; i < data->seek_index_next; i++) {
|
||||
if (data->seek_index[i].frame_no > frame_pos) {
|
||||
// previous chunk contains the frame we wanted
|
||||
// we can only seek to chunk boundaries
|
||||
frame_pos = data->seek_index[i-1].frame_no;
|
||||
if (!readOnly) {
|
||||
data->current_chunk = i-1; // position file to start of chunk
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (i+1 == data->seek_index_next) {
|
||||
// Last chunk
|
||||
frame_pos = data->seek_index[i].frame_no;
|
||||
if (!readOnly) {
|
||||
data->current_chunk = i; // position file to start of chunk
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
} else if (stream->is_video) {
|
||||
// iterate over all index entries of the stream,
|
||||
// there is one entry per frame (TODO: actually one per field,
|
||||
// if I interprete the documentation correctly...)
|
||||
int64 pos = 0;
|
||||
int64 lastKeyframePos = 0;
|
||||
int64 lastKeyframeIndex = 0;
|
||||
for (uint32 i = 0; i < data->seek_index_next; i++) {
|
||||
// remember the last known keyframe index/frame
|
||||
if (data->seek_index[i].keyframe) {
|
||||
lastKeyframePos = pos;
|
||||
lastKeyframeIndex = i;
|
||||
TRACE("keyframe at index %ld, frame %ld (seek: %ld)\n", i,
|
||||
pos, frame_pos);
|
||||
}
|
||||
|
||||
if (seekTo & B_MEDIA_SEEK_CLOSEST_BACKWARD) {
|
||||
// use the index and frame of the last keyframe
|
||||
if (pos == frame_pos) {
|
||||
frame_pos = lastKeyframePos;
|
||||
if (!readOnly)
|
||||
data->current_chunk = lastKeyframeIndex;
|
||||
goto done;
|
||||
}
|
||||
} else if (seekTo & B_MEDIA_SEEK_CLOSEST_FORWARD) {
|
||||
// use the index and frame of the last keyframe
|
||||
// if this frame is a keyframe and we at or past
|
||||
// the seek position
|
||||
if (pos >= frame_pos && pos == lastKeyframePos) {
|
||||
frame_pos = lastKeyframePos;
|
||||
if (!readOnly)
|
||||
data->current_chunk = lastKeyframeIndex;
|
||||
goto done;
|
||||
}
|
||||
} else {
|
||||
// ignore keyframes
|
||||
if (pos == frame_pos) {
|
||||
if (!readOnly)
|
||||
data->current_chunk = i;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
} else {
|
||||
return B_BAD_VALUE;
|
||||
}
|
||||
|
||||
ERROR("libOpenDML: seek failed, position not found\n");
|
||||
return B_ERROR;
|
||||
|
||||
done:
|
||||
TRACE("seek done: index: pos %d\n", data->current_chunk);
|
||||
// recalculate frame and time after seek
|
||||
*frame = frame_pos;
|
||||
*time = (frame_pos * 1000000LL * stream->frames_per_sec_scale)
|
||||
/ stream->frames_per_sec_rate;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
void
|
||||
Index::AddIndex(int stream_index, off_t position, uint32 size, uint64 frame, bigtime_t pts, bool keyframe) {
|
||||
|
||||
IndexEntry seek_index;
|
||||
|
||||
// Should be in a constructor
|
||||
seek_index.frame_no = frame;
|
||||
seek_index.position = position;
|
||||
seek_index.size = size;
|
||||
seek_index.pts = pts;
|
||||
seek_index.keyframe = keyframe;
|
||||
|
||||
fStreamData[stream_index].seek_index.push_back(seek_index);
|
||||
fStreamData[stream_index].seek_index_next++;
|
||||
}
|
||||
|
||||
void
|
||||
Index::DumpIndex(int stream_index)
|
||||
{
|
||||
IndexEntry _index;
|
||||
for (uint32 i = 0; i < fStreamData[stream_index].seek_index_next; i++) {
|
||||
_index = fStreamData[stream_index].seek_index[i];
|
||||
printf("Frame %Ld, pos %Ld, size %ld, pts %Ld\n",_index.frame_no, _index.position, _index.size, _index.pts);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,12 +25,45 @@
|
||||
#ifndef _INDEX_H
|
||||
#define _INDEX_H
|
||||
|
||||
|
||||
#include <SupportDefs.h>
|
||||
#include <vector>
|
||||
|
||||
/*
|
||||
This class handles all indexing of an AVI file
|
||||
Subclasses should override Init and create index entries based on the
|
||||
specialised index.
|
||||
|
||||
Seek and GetNextChunk will then work.
|
||||
|
||||
Current known subclasses are:
|
||||
Standard Index - Original AVI index idx1
|
||||
OpenDMLIndex - Open DML Standard Index
|
||||
FallBackIndex - Index created from the movi chunk
|
||||
*/
|
||||
|
||||
|
||||
class BPositionIO;
|
||||
class OpenDMLParser;
|
||||
|
||||
class IndexEntry {
|
||||
public:
|
||||
IndexEntry() {frame_no = 0;position=0;size=0;pts=0;keyframe=false;};
|
||||
|
||||
uint64 frame_no; // frame_no or sample_no
|
||||
off_t position; // The offset in the stream where the frame is
|
||||
uint32 size; // The size of the data available
|
||||
bigtime_t pts; // Presentation Time Stamp for this frame
|
||||
bool keyframe; // Is this a keyframe.
|
||||
};
|
||||
|
||||
class MediaStream {
|
||||
public:
|
||||
MediaStream() {seek_index_next=0;current_chunk=0;} ;
|
||||
~MediaStream() {seek_index.clear();};
|
||||
std::vector<IndexEntry> seek_index;
|
||||
uint64 seek_index_next;
|
||||
uint64 current_chunk;
|
||||
};
|
||||
|
||||
class Index {
|
||||
public:
|
||||
@@ -39,14 +72,22 @@ public:
|
||||
|
||||
virtual status_t Init() = 0;
|
||||
|
||||
virtual status_t GetNextChunkInfo(int stream_index, int64 *start,
|
||||
uint32 *size, bool *keyframe) = 0;
|
||||
virtual status_t Seek(int stream_index, uint32 seekTo, int64 *frame,
|
||||
bigtime_t *time, bool readOnly) = 0;
|
||||
status_t GetNextChunkInfo(int stream_index, off_t *start,
|
||||
uint32 *size, bool *keyframe);
|
||||
|
||||
status_t Seek(int stream_index, uint32 seekTo, int64 *frame,
|
||||
bigtime_t *time, bool readOnly);
|
||||
|
||||
void AddIndex(int stream_index, off_t position, uint32 size, uint64 frame, bigtime_t pts, bool keyframe);
|
||||
void DumpIndex(int stream_index);
|
||||
|
||||
protected:
|
||||
BPositionIO * fSource;
|
||||
OpenDMLParser * fParser;
|
||||
int fStreamCount;
|
||||
|
||||
private:
|
||||
std::vector<MediaStream> fStreamData;
|
||||
};
|
||||
|
||||
#endif // _INDEX_H
|
||||
|
||||
@@ -108,6 +108,7 @@ OpenDMLFile::Init()
|
||||
fIndex = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fIndex && fParser->StandardIndexSize() != 0) {
|
||||
fIndex = new StandardIndex(fSource, fParser);
|
||||
if (fIndex->Init() < B_OK) {
|
||||
@@ -115,6 +116,7 @@ OpenDMLFile::Init()
|
||||
fIndex = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fIndex) {
|
||||
fIndex = new FallbackIndex(fSource, fParser);
|
||||
if (fIndex->Init() < B_OK) {
|
||||
@@ -125,6 +127,8 @@ OpenDMLFile::Init()
|
||||
}
|
||||
}
|
||||
|
||||
// fIndex->DumpIndex(1);
|
||||
|
||||
TRACE("OpenDMLFile::SetTo: this is a %s AVI file with %d streams\n", fParser->OdmlExtendedHeader() ? "OpenDML" : "standard", fParser->StreamCount());
|
||||
|
||||
return B_OK;
|
||||
@@ -385,7 +389,7 @@ OpenDMLFile::AviGetNextChunkInfo(int stream_index, int64 *start, uint32 *size, b
|
||||
|
||||
|
||||
status_t
|
||||
OpenDMLFile::GetNextChunkInfo(int stream_index, int64 *start, uint32 *size,
|
||||
OpenDMLFile::GetNextChunkInfo(int stream_index, off_t *start, uint32 *size,
|
||||
bool *keyframe)
|
||||
{
|
||||
return fIndex->GetNextChunkInfo(stream_index, start, size, keyframe);
|
||||
@@ -470,7 +474,7 @@ OpenDMLFile::StreamFormat(int stream_index)
|
||||
&fParser->StreamInfo(stream_index)->stream_header : 0;
|
||||
}
|
||||
|
||||
const stream_info *
|
||||
const OpenDMLStream *
|
||||
OpenDMLFile::StreamInfo(int index)
|
||||
{
|
||||
return fParser->StreamInfo(index);
|
||||
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
|
||||
int StreamCount();
|
||||
|
||||
const stream_info * StreamInfo(int index);
|
||||
const OpenDMLStream * StreamInfo(int index);
|
||||
|
||||
/*
|
||||
bigtime_t Duration();
|
||||
@@ -57,7 +57,7 @@ public:
|
||||
const bitmap_info_header * VideoFormat(int stream_index);
|
||||
const avi_stream_header * StreamFormat(int stream_index);
|
||||
|
||||
status_t GetNextChunkInfo(int stream_index, int64 *start,
|
||||
status_t GetNextChunkInfo(int stream_index, off_t *start,
|
||||
uint32 *size, bool *keyframe);
|
||||
status_t Seek(int stream_index, uint32 seekTo, int64 *frame,
|
||||
bigtime_t *time, bool readOnly);
|
||||
|
||||
@@ -43,21 +43,3 @@ OpenDMLIndex::Init()
|
||||
{
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
OpenDMLIndex::GetNextChunkInfo(int stream_index, int64 *start, uint32 *size,
|
||||
bool *keyframe)
|
||||
{
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
OpenDMLIndex::Seek(int stream_index, uint32 seekTo, int64 *frame,
|
||||
bigtime_t *time, bool readOnly)
|
||||
{
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -34,11 +34,6 @@ public:
|
||||
~OpenDMLIndex();
|
||||
|
||||
status_t Init();
|
||||
|
||||
status_t GetNextChunkInfo(int stream_index, int64* start,
|
||||
uint32* size, bool* keyframe);
|
||||
status_t Seek(int stream_index, uint32 seekTo, int64* frame,
|
||||
bigtime_t* time, bool readOnly);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include "OpenDMLParser.h"
|
||||
#include "avi.h"
|
||||
|
||||
//#define TRACE_ODML_PARSER
|
||||
#define TRACE_ODML_PARSER
|
||||
#ifdef TRACE_ODML_PARSER
|
||||
#define TRACE printf
|
||||
#else
|
||||
@@ -68,16 +68,13 @@ OpenDMLParser::StreamCount()
|
||||
return fStreamCount;
|
||||
}
|
||||
|
||||
const stream_info *
|
||||
const OpenDMLStream *
|
||||
OpenDMLParser::StreamInfo(int index)
|
||||
{
|
||||
if (index < 0 || index >= fStreamCount)
|
||||
return NULL;
|
||||
|
||||
stream_info *info = fStreams;
|
||||
while (index--)
|
||||
info = info->next;
|
||||
return info;
|
||||
return &fStreams[index];
|
||||
}
|
||||
|
||||
int64
|
||||
@@ -113,30 +110,23 @@ OpenDMLParser::OdmlExtendedHeader()
|
||||
void
|
||||
OpenDMLParser::CreateNewStreamInfo()
|
||||
{
|
||||
stream_info *info = new stream_info;
|
||||
info->next = 0;
|
||||
info->is_audio = false;
|
||||
info->is_video = false;
|
||||
info->stream_header_valid = false;
|
||||
info->audio_format = 0;
|
||||
info->video_format_valid = false;
|
||||
info->odml_index_start = 0;
|
||||
info->odml_index_size = 0;
|
||||
info->duration = 0;
|
||||
info->frame_count = 0;
|
||||
info->frames_per_sec_rate = 1;
|
||||
info->frames_per_sec_scale = 1;
|
||||
OpenDMLStream info;
|
||||
|
||||
info.is_audio = false;
|
||||
info.is_video = false;
|
||||
info.stream_header_valid = false;
|
||||
info.audio_format = 0;
|
||||
info.video_format_valid = false;
|
||||
info.odml_index_start = 0;
|
||||
info.odml_index_size = 0;
|
||||
info.duration = 0;
|
||||
info.frame_count = 0;
|
||||
info.frames_per_sec_rate = 1;
|
||||
info.frames_per_sec_scale = 1;
|
||||
|
||||
// append the new stream_info to the fStreams list and point fCurrentStream to it
|
||||
if (fStreams) {
|
||||
stream_info *cur = fStreams;
|
||||
while (cur->next)
|
||||
cur = cur->next;
|
||||
cur->next = info;
|
||||
} else {
|
||||
fStreams = info;
|
||||
}
|
||||
fCurrentStream = info;
|
||||
fStreams.push_back(info);
|
||||
|
||||
fCurrentStream = fStreams.last();
|
||||
}
|
||||
|
||||
status_t
|
||||
@@ -176,13 +166,13 @@ OpenDMLParser::Init()
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < fStreamCount; i++) {
|
||||
SetupStreamLength(const_cast<stream_info *>(StreamInfo(i)));
|
||||
SetupStreamLength(const_cast<OpenDMLStream *>(StreamInfo(i)));
|
||||
}
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
void
|
||||
OpenDMLParser::SetupStreamLength(stream_info *stream)
|
||||
OpenDMLParser::SetupStreamLength(OpenDMLStream *stream)
|
||||
{
|
||||
if (stream->is_audio)
|
||||
SetupAudioStreamLength(stream);
|
||||
@@ -193,7 +183,7 @@ OpenDMLParser::SetupStreamLength(stream_info *stream)
|
||||
// F:\avi-info\Information on AVI file.htm
|
||||
|
||||
void
|
||||
OpenDMLParser::SetupAudioStreamLength(stream_info *stream)
|
||||
OpenDMLParser::SetupAudioStreamLength(OpenDMLStream *stream)
|
||||
{
|
||||
stream->frame_count = stream->stream_header.length;
|
||||
|
||||
@@ -201,43 +191,50 @@ OpenDMLParser::SetupAudioStreamLength(stream_info *stream)
|
||||
&& stream->stream_header.sample_size != 0
|
||||
&& stream->stream_header.sample_size != 1) { // PCM
|
||||
stream->frame_count /= (stream->stream_header.sample_size + 7) / 8;
|
||||
TRACE("audio: messing up PCM frame_count?\n");
|
||||
TRACE("audio: messed up PCM frame_count?\n");
|
||||
}
|
||||
|
||||
if (stream->stream_header.rate && stream->stream_header.scale) {
|
||||
stream->frames_per_sec_rate = stream->stream_header.rate;
|
||||
stream->frames_per_sec_scale = stream->stream_header.scale;
|
||||
stream->duration = (stream->frame_count * stream->frames_per_sec_scale * 1000000) / stream->frames_per_sec_rate;
|
||||
TRACE("audio: using rate+scale\n");
|
||||
stream->duration = (stream->frame_count * stream->frames_per_sec_scale * 1000000LL) / stream->frames_per_sec_rate;
|
||||
TRACE("audio: duration calculated using rate+scale\n");
|
||||
} else if (stream->audio_format->avg_bytes_per_sec) {
|
||||
stream->frames_per_sec_rate = stream->audio_format->avg_bytes_per_sec;
|
||||
stream->frames_per_sec_scale = 1;
|
||||
stream->duration = (stream->frame_count * stream->frames_per_sec_scale * 1000000) / stream->frames_per_sec_rate;
|
||||
TRACE("audio: using avg_bytes_per_sec\n");
|
||||
stream->duration = (stream->frame_count * stream->frames_per_sec_scale * 1000000LL) / stream->frames_per_sec_rate;
|
||||
TRACE("audio: duration calculated using avg_bytes_per_sec\n");
|
||||
} else if (AviMainHeader()->micro_sec_per_frame) {
|
||||
uint32 video_frame_count = OdmlExtendedHeader() ? OdmlExtendedHeader()->total_frames : AviMainHeader()->total_frames;
|
||||
stream->duration = video_frame_count * AviMainHeader()->micro_sec_per_frame;
|
||||
stream->frames_per_sec_rate = (stream->frame_count * 1000 * 1000000) / stream->duration;
|
||||
stream->frames_per_sec_rate = (stream->frame_count * 1000 * 1000000LL) / stream->duration;
|
||||
stream->frames_per_sec_scale = 1000;
|
||||
TRACE("audio: using micro_sec_per_frame\n");
|
||||
TRACE("audio: duration calculated using micro_sec_per_frame\n");
|
||||
} else {
|
||||
TRACE("audio: no idea what to do\n");
|
||||
TRACE("audio: duration could not be calculated no idea what to do\n");
|
||||
}
|
||||
|
||||
if (stream->audio_format->avg_bytes_per_sec) {
|
||||
int64 expectedFrameCount
|
||||
= (stream->duration * stream->audio_format->avg_bytes_per_sec)
|
||||
/ 1000000;
|
||||
TRACE("audio: expected frame_count %lld, calculated stream "
|
||||
"frame_count %lld\n", expectedFrameCount, stream->frame_count);
|
||||
if (expectedFrameCount * 9 > stream->frame_count * 10) {
|
||||
TRACE("audio: something is wrong, ignoring calculated stream "
|
||||
"frame_count, rate and scale\n");
|
||||
stream->frame_count = expectedFrameCount;
|
||||
stream->frames_per_sec_rate
|
||||
= stream->audio_format->avg_bytes_per_sec;
|
||||
// The stream details are often wrong, if there is a audio format structure we attempt to use that
|
||||
if (stream->audio_format->format_tag == 0x0001) { // RAW PCM
|
||||
if (stream->audio_format->avg_bytes_per_sec) {
|
||||
int64 expectedFrameCount
|
||||
= (stream->duration * stream->audio_format->avg_bytes_per_sec)
|
||||
/ 1000000;
|
||||
TRACE("audio: expected frame_count %lld, stream "
|
||||
"frame_count %lld\n", expectedFrameCount, stream->frame_count);
|
||||
if (expectedFrameCount * 9 > stream->frame_count * 10) {
|
||||
TRACE("audio: something is wrong, ignoring stream frame_count\n");
|
||||
stream->frame_count = expectedFrameCount;
|
||||
}
|
||||
|
||||
stream->frames_per_sec_rate = stream->audio_format->avg_bytes_per_sec;
|
||||
stream->frames_per_sec_scale = 1;
|
||||
}
|
||||
} else {
|
||||
// encoded format usually has audio format details correct so always use them
|
||||
// stream->frames_per_sec_rate = stream->audio_format->frames_per_sec;
|
||||
// stream->frames_per_sec_scale = 1;
|
||||
stream->frame_count = stream->duration * stream->audio_format->frames_per_sec / 1000000LL;
|
||||
}
|
||||
|
||||
TRACE("audio: frame_count %lld, duration %.6f, fps %.3f\n",
|
||||
@@ -246,7 +243,7 @@ OpenDMLParser::SetupAudioStreamLength(stream_info *stream)
|
||||
}
|
||||
|
||||
void
|
||||
OpenDMLParser::SetupVideoStreamLength(stream_info *stream)
|
||||
OpenDMLParser::SetupVideoStreamLength(OpenDMLStream *stream)
|
||||
{
|
||||
stream->frame_count = stream->stream_header.length;
|
||||
if (stream->stream_header.rate && stream->stream_header.scale) {
|
||||
@@ -410,8 +407,8 @@ OpenDMLParser::ParseChunk_AVI(int number, uint64 start, uint32 size)
|
||||
}
|
||||
|
||||
if (Chunksize == 0) {
|
||||
ERROR("OpenDMLParser::ParseChunk_AVI: chunk '"FOURCC_FORMAT"' has size 0\n", FOURCC_PARAM(Chunkfcc));
|
||||
return B_ERROR;
|
||||
TRACE("OpenDMLParser::ParseChunk_AVI: chunk '"FOURCC_FORMAT"' has size 0\n", FOURCC_PARAM(Chunkfcc));
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
if (Chunkfcc == FOURCC('L','I','S','T')) {
|
||||
@@ -893,8 +890,10 @@ OpenDMLParser::ParseList_movi(uint64 start, uint32 size)
|
||||
{
|
||||
TRACE("OpenDMLParser::ParseList_movi, size %lu\n", size);
|
||||
|
||||
if (fMovieListStart == 0)
|
||||
if (fMovieListStart == 0) {
|
||||
fMovieListStart = start;
|
||||
fMovieListSize = size;
|
||||
}
|
||||
|
||||
fMovieChunkCount++;
|
||||
return B_OK;
|
||||
|
||||
@@ -26,19 +26,30 @@
|
||||
#define _OPEN_DML_PARSER_H
|
||||
|
||||
#include <DataIO.h>
|
||||
#include <vector>
|
||||
|
||||
#include "avi.h"
|
||||
|
||||
struct stream_info
|
||||
class OpenDMLStream
|
||||
{
|
||||
stream_info * next; // TODO: replace with a vector<>
|
||||
public:
|
||||
OpenDMLStream();
|
||||
~OpenDMLStream();
|
||||
|
||||
bool is_audio;
|
||||
bool is_video;
|
||||
bool is_subtitle;
|
||||
|
||||
bool stream_header_valid;
|
||||
avi_stream_header stream_header;
|
||||
|
||||
bool audio_format_valid;
|
||||
wave_format_ex *audio_format;
|
||||
size_t audio_format_size;
|
||||
|
||||
bool video_format_valid;
|
||||
bitmap_info_header video_format;
|
||||
|
||||
int64 odml_index_start;
|
||||
uint32 odml_index_size;
|
||||
|
||||
@@ -58,12 +69,13 @@ public:
|
||||
|
||||
int StreamCount();
|
||||
|
||||
const stream_info * StreamInfo(int index);
|
||||
const OpenDMLStream * StreamInfo(int index);
|
||||
|
||||
int64 StandardIndexStart();
|
||||
uint32 StandardIndexSize();
|
||||
|
||||
int64 MovieListStart();
|
||||
uint32 MovieListSize() {return fMovieListSize;};
|
||||
|
||||
const avi_main_header * AviMainHeader();
|
||||
const odml_extended_header * OdmlExtendedHeader();
|
||||
@@ -85,16 +97,18 @@ private:
|
||||
status_t ParseList_strl(uint64 start, uint32 size);
|
||||
|
||||
void CreateNewStreamInfo();
|
||||
void SetupStreamLength(stream_info *stream);
|
||||
void SetupAudioStreamLength(stream_info *stream);
|
||||
void SetupVideoStreamLength(stream_info *stream);
|
||||
void SetupStreamLength(OpenDMLStream *stream);
|
||||
void SetupAudioStreamLength(OpenDMLStream *stream);
|
||||
void SetupVideoStreamLength(OpenDMLStream *stream);
|
||||
|
||||
private:
|
||||
|
||||
BPositionIO * fSource;
|
||||
int64 fSize;
|
||||
|
||||
// TODO can be multiple Movi Lists
|
||||
int64 fMovieListStart;
|
||||
uint32 fMovieListSize;
|
||||
|
||||
int64 fStandardIndexStart;
|
||||
uint32 fStandardIndexSize;
|
||||
@@ -108,8 +122,8 @@ private:
|
||||
odml_extended_header fOdmlExtendedHeader;
|
||||
bool fOdmlExtendedHeaderValid;
|
||||
|
||||
stream_info * fStreams;
|
||||
stream_info * fCurrentStream;
|
||||
vector<OpenDMLStream> fStreams;
|
||||
OpenDMLStream * fCurrentStream;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
*/
|
||||
#include <SupportDefs.h>
|
||||
#include <DataIO.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <new>
|
||||
#include "ReaderPlugin.h" // B_MEDIA_*
|
||||
#include "StandardIndex.h"
|
||||
#include "OpenDMLParser.h"
|
||||
|
||||
@@ -48,8 +48,6 @@ StandardIndex::StandardIndex(BPositionIO *source, OpenDMLParser *parser)
|
||||
: Index(source, parser)
|
||||
, fIndex(NULL)
|
||||
, fIndexSize(0)
|
||||
, fStreamData(NULL)
|
||||
, fStreamCount(parser->StreamCount())
|
||||
, fDataOffset(parser->MovieListStart() - 4)
|
||||
{
|
||||
}
|
||||
@@ -58,11 +56,6 @@ StandardIndex::StandardIndex(BPositionIO *source, OpenDMLParser *parser)
|
||||
StandardIndex::~StandardIndex()
|
||||
{
|
||||
delete [] fIndex;
|
||||
if (fStreamData) {
|
||||
for (int i = 0; i < fStreamCount; i++)
|
||||
delete [] fStreamData[i].seek_hints;
|
||||
}
|
||||
delete [] fStreamData;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,12 +65,7 @@ StandardIndex::Init()
|
||||
uint32 indexBytes = fParser->StandardIndexSize();
|
||||
fIndexSize = indexBytes / sizeof(avi_standard_index_entry);
|
||||
indexBytes = fIndexSize * sizeof(avi_standard_index_entry);
|
||||
uint32 seekHintsStride = 1800 * fStreamCount;
|
||||
uint32 seekHintsMax = fIndexSize / seekHintsStride;
|
||||
|
||||
TRACE("StandardIndex::Init: seekHintsStride %lu\n", seekHintsStride);
|
||||
TRACE("StandardIndex::Init: seekHintsMax %lu\n", seekHintsMax);
|
||||
|
||||
#ifdef TRACE_START_INDEX
|
||||
{ BStopWatch w("StandardIndex::Init: malloc");
|
||||
#endif
|
||||
@@ -114,216 +102,78 @@ StandardIndex::Init()
|
||||
//DumpIndex();
|
||||
#endif
|
||||
|
||||
fStreamData = new stream_data[fStreamCount];
|
||||
for (int i = 0; i < fStreamCount; i++) {
|
||||
fStreamData[i].chunk_id = i / 10 + '0' + (i % 10 + '0') * 256;
|
||||
fStreamData[i].chunk_count = 0;
|
||||
fStreamData[i].keyframe_count = 0;
|
||||
fStreamData[i].stream_pos = 0;
|
||||
fStreamData[i].stream_size = 0;
|
||||
fStreamData[i].seek_hints = new seek_hint[seekHintsMax];
|
||||
fStreamData[i].seek_hints_count = 0;
|
||||
fStreamData[i].seek_hints_next = seekHintsStride;
|
||||
}
|
||||
|
||||
#ifdef TRACE_START_INDEX
|
||||
{ BStopWatch w("StandardIndex::Init: scan index");
|
||||
#endif
|
||||
|
||||
for (int stream = 0; stream < fStreamCount; stream++) {
|
||||
uint32 chunk_id = fStreamData[stream].chunk_id;
|
||||
uint64 stream_size = 0;
|
||||
uint32 chunk_count = 0;
|
||||
uint32 keyframe_count = 0;
|
||||
uint32 seek_hints_next = seekHintsStride;
|
||||
uint32 seek_hints_count = 0;
|
||||
for (uint32 i = 0; i < fIndexSize; i++) {
|
||||
if ((fIndex[i].chunk_id & 0xffff) == chunk_id) {
|
||||
stream_size += fIndex[i].chunk_length;
|
||||
chunk_count++;
|
||||
keyframe_count += (fIndex[i].flags >> AVIIF_KEYFRAME_SHIFT) & 1;
|
||||
|
||||
if (i >= seek_hints_next) {
|
||||
seek_hints_next = i + seekHintsStride;
|
||||
seek_hint *hint = &fStreamData[stream].seek_hints[
|
||||
seek_hints_count++];
|
||||
hint->stream_pos = fIndex[i].chunk_offset;
|
||||
hint->index_pos = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
fStreamData[stream].stream_size = stream_size;
|
||||
fStreamData[stream].chunk_count = chunk_count;
|
||||
fStreamData[stream].keyframe_count = keyframe_count;
|
||||
fStreamData[stream].seek_hints_count = seek_hints_count;
|
||||
}
|
||||
#ifdef TRACE_START_INDEX
|
||||
}
|
||||
|
||||
for (int i = 0; i < fStreamCount; i++) {
|
||||
printf("stream %d, stream_size %llu\n", i, fStreamData[i].stream_size);
|
||||
printf("stream %d, chunk_count %lu\n", i, fStreamData[i].chunk_count);
|
||||
printf("stream %d, keyframe_count %lu\n", i,
|
||||
fStreamData[i].keyframe_count);
|
||||
printf("stream %d, seek_hints_count %lu\n", i,
|
||||
fStreamData[i].seek_hints_count);
|
||||
for (int j = 0; j < fStreamData[i].seek_hints_count; j++) {
|
||||
printf(" seek_hint %3d, index_pos %6lu, stream_pos %lld\n", j,
|
||||
fStreamData[i].seek_hints[j].index_pos,
|
||||
fStreamData[i].seek_hints[j].stream_pos);
|
||||
}
|
||||
}
|
||||
#endif // TRACE_START_INDEX
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StandardIndex::DumpIndex()
|
||||
{
|
||||
uint32 chunk = fIndex->chunk_id;
|
||||
int count = 0;
|
||||
int pos = 0;
|
||||
|
||||
printf("StandardIndex::DumpIndex %lu entries\n", fIndexSize);
|
||||
for (uint32 i = 0; i < fIndexSize; i++) {
|
||||
count++;
|
||||
if (chunk != fIndex[i].chunk_id) {
|
||||
printf("%3d %c%c%c%c", count, FOURCC_PARAM(chunk));
|
||||
chunk = fIndex[i].chunk_id;
|
||||
count = 0;
|
||||
if (++pos % 8 == 0)
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
if (count)
|
||||
printf("%3d %c%c%c%c", count, FOURCC_PARAM(chunk));
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
StandardIndex::GetNextChunkInfo(int stream_index, int64 *start, uint32 *size,
|
||||
bool *keyframe)
|
||||
{
|
||||
stream_data *data = &fStreamData[stream_index];
|
||||
while (data->stream_pos < fIndexSize) {
|
||||
if ((fIndex[data->stream_pos].chunk_id & 0xffff) == data->chunk_id) {
|
||||
*keyframe = fIndex[data->stream_pos].flags & AVIIF_KEYFRAME;
|
||||
*start = fDataOffset + fIndex[data->stream_pos].chunk_offset + 8;
|
||||
// skip 8 bytes (chunk id + chunk size)
|
||||
*size = fIndex[data->stream_pos].chunk_length;
|
||||
data->stream_pos++;
|
||||
return B_OK;
|
||||
}
|
||||
data->stream_pos++;
|
||||
}
|
||||
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
StandardIndex::Seek(int stream_index, uint32 seekTo, int64 *frame,
|
||||
bigtime_t *time, bool readOnly)
|
||||
{
|
||||
TRACE("StandardIndex::Seek: stream %d, seekTo%s%s%s%s, time %Ld, "
|
||||
"frame %Ld\n", stream_index,
|
||||
(seekTo & B_MEDIA_SEEK_TO_TIME) ? " B_MEDIA_SEEK_TO_TIME" : "",
|
||||
(seekTo & B_MEDIA_SEEK_TO_FRAME) ? " B_MEDIA_SEEK_TO_FRAME" : "",
|
||||
(seekTo & B_MEDIA_SEEK_CLOSEST_FORWARD) ?
|
||||
" B_MEDIA_SEEK_CLOSEST_FORWARD" : "",
|
||||
(seekTo & B_MEDIA_SEEK_CLOSEST_BACKWARD) ?
|
||||
" B_MEDIA_SEEK_CLOSEST_BACKWARD" : "",
|
||||
*time, *frame);
|
||||
int stream_index;
|
||||
off_t position;
|
||||
uint32 size;
|
||||
uint64 frame[fStreamCount];
|
||||
uint64 frame_no;
|
||||
bigtime_t pts = 0;
|
||||
bool keyframe;
|
||||
uint32 sample_size;
|
||||
|
||||
const stream_info *stream = fParser->StreamInfo(stream_index);
|
||||
stream_data *data = &fStreamData[stream_index];
|
||||
const OpenDMLStream *stream;
|
||||
|
||||
int64 frame_pos;
|
||||
if (seekTo & B_MEDIA_SEEK_TO_FRAME)
|
||||
frame_pos = *frame;
|
||||
else if (seekTo & B_MEDIA_SEEK_TO_TIME) {
|
||||
frame_pos = (*time * stream->frames_per_sec_rate)
|
||||
/ (1000000 * stream->frames_per_sec_scale);
|
||||
} else
|
||||
return B_BAD_VALUE;
|
||||
for (uint32 i=0;i < fStreamCount; i++) {
|
||||
frame[i] = 0;
|
||||
}
|
||||
|
||||
if (stream->is_audio) {
|
||||
// TODO: Actually take keyframe flags into account!
|
||||
int64 bytes = 0;
|
||||
for (uint32 i = 0; i < fIndexSize; i++) {
|
||||
if ((fIndex[i].chunk_id & 0xffff) == data->chunk_id) {
|
||||
int64 bytesNext = bytes + fIndex[i].chunk_length;
|
||||
if (bytes <= frame_pos && bytesNext > frame_pos) {
|
||||
if (!readOnly)
|
||||
data->stream_pos = i;
|
||||
goto done;
|
||||
}
|
||||
bytes = bytesNext;
|
||||
}
|
||||
}
|
||||
} else if (stream->is_video) {
|
||||
// iterate over all index entries of the stream,
|
||||
// there is one entry per frame (TODO: actually one per field,
|
||||
// if I interprete the documentation correctly...)
|
||||
int pos = 0;
|
||||
int lastKeyframePos = 0;
|
||||
int lastKeyframeIndex = 0;
|
||||
for (uint32 i = 0; i < fIndexSize; i++) {
|
||||
// ignore index entries which are not for this stream
|
||||
if ((fIndex[i].chunk_id & 0xffff) != data->chunk_id)
|
||||
continue;
|
||||
for (uint32 i = 0; i < fIndexSize; i++) {
|
||||
|
||||
stream_index = ((fIndex[i].chunk_id & 0xff) - '0') * 10;
|
||||
stream_index += ((fIndex[i].chunk_id >> 8) & 0xff) - '0';
|
||||
|
||||
// remember the last known keyframe index/frame
|
||||
if (fIndex[i].flags & AVIIF_KEYFRAME) {
|
||||
lastKeyframePos = pos;
|
||||
lastKeyframeIndex = i;
|
||||
TRACE("keyframe at index %ld, frame %ld (seek: %ld)\n", i,
|
||||
pos, frame_pos);
|
||||
}
|
||||
stream = fParser->StreamInfo(stream_index);
|
||||
|
||||
keyframe = (fIndex[i].flags >> AVIIF_KEYFRAME_SHIFT) & 1;
|
||||
size = fIndex[i].chunk_length;
|
||||
|
||||
// Some muxers write chunk_offset as non-relative, need to handle this case.
|
||||
position = fDataOffset + fIndex[i].chunk_offset;
|
||||
frame_no = frame[stream_index];
|
||||
|
||||
if (seekTo & B_MEDIA_SEEK_CLOSEST_BACKWARD) {
|
||||
// use the index and frame of the last keyframe
|
||||
if (pos == frame_pos) {
|
||||
frame_pos = lastKeyframePos;
|
||||
if (!readOnly)
|
||||
data->stream_pos = lastKeyframeIndex;
|
||||
goto done;
|
||||
}
|
||||
} else if (seekTo & B_MEDIA_SEEK_CLOSEST_FORWARD) {
|
||||
// use the index and frame of the last keyframe
|
||||
// if this frame is a keyframe and we at or past
|
||||
// the seek position
|
||||
if (pos >= frame_pos && pos == lastKeyframePos) {
|
||||
frame_pos = lastKeyframePos;
|
||||
if (!readOnly)
|
||||
data->stream_pos = lastKeyframeIndex;
|
||||
goto done;
|
||||
if (stream->is_video) {
|
||||
// Video is easy enough, it is always 1 frame = 1 index
|
||||
pts = frame[stream_index] * 1000000LL * stream->frames_per_sec_scale / stream->frames_per_sec_rate;
|
||||
frame[stream_index]++;
|
||||
} else if (stream->is_audio) {
|
||||
|
||||
pts = frame[stream_index] * 1000000LL / stream->audio_format->frames_per_sec;
|
||||
|
||||
// Audio varies based on many different hacks over the years
|
||||
// The simplest is chunk size / sample size = no of samples in the chunk for uncompressed audio
|
||||
// ABR Compressed audio is more difficult and VBR Compressed audio is even harder
|
||||
// What follows is what I have worked out from various sources across the internet.
|
||||
if (stream->audio_format->format_tag != 0x0001) {
|
||||
// VBR audio is detected as having a block_align >= 960
|
||||
if (stream->audio_format->block_align >= 960) {
|
||||
// VBR Audio so block_align is the largest no of samples in a chunk
|
||||
// scale is the no of samples in a frame
|
||||
// rate is the sample rate
|
||||
// but we must round up when calculating no of frames in a chunk
|
||||
frame[stream_index] += (uint64)(ceil((double)size / (double)stream->audio_format->block_align)) * stream->frames_per_sec_scale;
|
||||
} else {
|
||||
// ABR Audio so use Chunk Size and average bytes per second to determine how many samples there are in the chunk
|
||||
frame[stream_index] += (uint64)(ceil((double)size / (double)stream->audio_format->block_align)) * stream->audio_format->frames_per_sec / stream->audio_format->avg_bytes_per_sec;
|
||||
}
|
||||
} else {
|
||||
// ignore keyframes
|
||||
if (pos == frame_pos) {
|
||||
if (!readOnly)
|
||||
data->stream_pos = i;
|
||||
goto done;
|
||||
// sample size can be corrupt
|
||||
if (stream->stream_header.sample_size > 0) {
|
||||
sample_size = stream->stream_header.sample_size;
|
||||
} else {
|
||||
// compute sample size
|
||||
sample_size = stream->audio_format->bits_per_sample * stream->audio_format->channels / 8;
|
||||
}
|
||||
frame[stream_index] += size / stream->stream_header.sample_size;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
} else {
|
||||
return B_BAD_VALUE;
|
||||
|
||||
AddIndex(stream_index, position, size, frame_no, pts, keyframe);
|
||||
}
|
||||
|
||||
ERROR("libOpenDML: seek failed, position not found\n");
|
||||
return B_ERROR;
|
||||
|
||||
done:
|
||||
TRACE("seek done: index: pos %d, size %d\n", data->stream_pos, fIndexSize);
|
||||
*frame = frame_pos;
|
||||
*time = (frame_pos * 1000000 * stream->frames_per_sec_scale)
|
||||
/ stream->frames_per_sec_rate;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,37 +36,9 @@ public:
|
||||
|
||||
status_t Init();
|
||||
|
||||
status_t GetNextChunkInfo(int stream_index, int64* start,
|
||||
uint32* size, bool* keyframe);
|
||||
status_t Seek(int stream_index, uint32 seekTo, int64* frame,
|
||||
bigtime_t* time, bool readOnly);
|
||||
|
||||
private:
|
||||
void DumpIndex();
|
||||
|
||||
private:
|
||||
struct seek_hint
|
||||
{
|
||||
uint64 stream_pos;
|
||||
uint32 index_pos;
|
||||
};
|
||||
|
||||
struct stream_data
|
||||
{
|
||||
uint32 chunk_id;
|
||||
uint32 chunk_count;
|
||||
uint32 keyframe_count;
|
||||
uint32 stream_pos;
|
||||
uint64 stream_size;
|
||||
seek_hint * seek_hints;
|
||||
int seek_hints_count;
|
||||
uint32 seek_hints_next;
|
||||
};
|
||||
|
||||
avi_standard_index_entry *fIndex;
|
||||
uint32 fIndexSize;
|
||||
stream_data * fStreamData;
|
||||
int fStreamCount;
|
||||
int64 fDataOffset;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user