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:
David McPaul
2009-02-24 08:12:27 +00:00
parent 3e02e6ae4d
commit 201970d309
12 changed files with 526 additions and 370 deletions
@@ -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;
};