mkvstream example - initial release

Change-Id: Iea0402764f62f47dc4e2c6e717867673acd043f3
This commit is contained in:
Matthew Heaney 2012-08-28 16:24:27 -07:00
parent 4a5141344e
commit dca6be3a32
3 changed files with 391 additions and 0 deletions

259
mkvstream.cc Normal file
View File

@ -0,0 +1,259 @@
// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.
#include "./mkvstream.h"
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <cstring>
MkvStream::MkvStream() : file_(NULL) {
}
MkvStream::~MkvStream() {
Close();
}
int MkvStream::Open(const char* filename) {
if (filename == NULL || file_ != NULL)
return -1;
file_ = fopen(filename, "rb");
if (file_ == NULL)
return -1;
total_ = -1; // means "end-of-file not reached yet"
avail_ = 0;
// Establish invariant
cache_.push_back(Page());
Page& page = cache_.back();
page.off_ = 0;
const int e = page.Read(this);
if (e < 0) {
Close();
return -1;
}
return 0;
}
void MkvStream::Close() {
if (file_) {
fclose(file_);
file_ = NULL;
cache_.clear();
}
}
int MkvStream::Read(long long pos, long len, unsigned char* buf) {
if (file_ == NULL)
return -1;
assert(!cache_.empty()); // invariant
assert(avail_ >= cache_.back().off_);
// For now, we only support sequential reading of streams, with no
// jumps backwards in the stream. Were this a real network cache,
// we would have to purge the cache then reissue another fetch over
// the wire. (To be precise, this operation would report a cache
// miss to the parser, which would be reported back to the caller as
// an underflow. The caller would then call PopulateCache directly,
// then re-try the parse.)
if (pos < 0)
return -1; // bad arg
if (pos < cache_.front().off_)
return -1; // attempt to read non-sequentially
if (total_ >= 0 && pos > total_)
return -1; // attempt to read beyond end-of-stream
if (len < 0)
return -1; // bad arg
if (len == 0)
return 0;
const long long end = pos + len;
if (total_ >= 0 && end > total_)
return -1; // attempt to read beyond end of stream
// Over a wire, network reads are slow, or block completely, which
// is not acceptable (e.g. it would leave you unable to pump windows
// messages). Hence the need for a cache. If we won't have enough
// data in the cache to service this read call, we report underflow
// to the parser, which in turn reports it back to the caller. The
// caller is then expected to populate the cache, using whatever
// mechanism is appropriate for the application (e.g. post an async
// read and wait for completion), and then re-try the parse. This
// is a simulator, so all the caller needs to do here is call
// PopulateCache, but in a real application with real network I/O,
// populating the cache can get very complex (especially when
// seeking is supported).
if (end > avail_) // not enough data in the cache
return mkvparser::E_BUFFER_NOT_FULL;
if (buf == NULL)
return -1;
typedef cache_t::const_iterator iter_t;
const iter_t i = cache_.begin();
const iter_t j = cache_.end();
const iter_t kk = std::upper_bound(i, j, pos, Page::Less());
iter_t k = --iter_t(kk);
while (len > 0) {
assert(pos + len == end);
const long long page_off = k->off_;
assert(page_off <= pos);
long long page_end = page_off + Page::kSize;
if (page_end > end)
page_end = end;
const unsigned char* const src = k->buf_ + (pos - page_off);
const long long count_ = page_end - pos;
assert(count_ <= len);
const size_t count = static_cast<size_t>(count_);
memcpy(buf, src, count);
pos += count;
len -= count;
buf += count;
}
assert(pos == end);
return 0;
}
int MkvStream::Length(long long* total, long long* avail) {
if (file_ == NULL || total == NULL || avail == NULL)
return -1;
*total = total_;
*avail = avail_;
return 0;
}
int MkvStream::PopulateCache(long long pos, long requested_len) {
if (file_ == NULL)
return -1;
assert(!cache_.empty());
assert(avail_ >= 0);
assert(total_ < 0 || total_ == avail_);
if (pos < 0)
return -1;
if (pos < cache_.front().off_)
return -1; // attempt to read non-sequentially
if (requested_len < 0)
return -1;
if (requested_len == 0)
return 0; //TODO(matthewjheaney): ensure pos in cache?
// Simulate a network read, which might not return all
// requested bytes immediately:
const long actual_len = 1 + rand() % requested_len;
const long long end = pos + actual_len;
long long off = cache_.back().off_;
assert(off % Page::kSize == 0);
assert(off <= avail_);
while (total_ < 0 && avail_ < end) {
cache_.push_back(Page());
Page& page = cache_.back();
off += Page::kSize;
page.off_ = off;
const int e = page.Read(this);
if (e < 0) // error
return -1;
assert(e == 0 || total_ >= 0);
}
return 0;
}
int MkvStream::PurgeCache(long long pos) {
if (file_ == NULL)
return -1;
if (pos < 0)
return -1;
assert(!cache_.empty());
if (pos < cache_.front().off_)
return 0;
typedef cache_t::iterator iter_t;
iter_t i = cache_.begin();
const iter_t j = cache_.end();
const iter_t kk = std::upper_bound(i, j, pos, Page::Less());
const iter_t k = --iter_t(kk);
while (i != k)
cache_.erase(i++);
return 0;
}
int MkvStream::Page::Read(MkvStream* stream) {
assert(stream);
assert(stream->total_ < 0);
assert(stream->avail_ >= 0);
assert(off_ % kSize == 0);
assert(off_ == stream->avail_);
FILE* const f = stream->file_;
assert(f);
unsigned char* dst = buf_;
for (int i = 0; i < kSize; ++i) {
const int c = fgetc(f);
if (c == EOF) {
if (!feof(f))
return -1;
stream->total_ = stream->avail_;
return 1;
}
*dst++ = static_cast<unsigned char>(c);
++stream->avail_;
}
return 0;
}

90
mkvstream.h Normal file
View File

@ -0,0 +1,90 @@
// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.
#ifndef MKVSTREAM_H_
#define MKVSTREAM_H_
#include <cstdio>
#include <deque>
#include "./mkvparser.hpp"
class MkvStream : public mkvparser::IMkvReader {
public:
MkvStream();
virtual ~MkvStream();
// Open the file identified by |filename| in read-only mode, as a
// binary stream of bytes. Returns 0 on success, negative if error.
int Open(const char* filename);
// Closes the file stream. Note that the stream is automatically
// closed when the MkvStream object is destroyed.
void Close();
// Fetches |len| bytes of data from the cache, started at absolute
// stream position |pos|, reading into buffer |buf|. Returns
// negative value if error (including mkvparser::E_BUFFER_NOT_FULL,
// to indicate that not all of the requested bytes were in the
// cache), 0 on success (all requested bytes were returned).
virtual int Read(long long pos, long len, unsigned char* buf);
// The |total| argument indicates how many total bytes are in the
// stream. This network simulator sets |total| to -1 until we reach
// end-of-stream, at which point |total| is set to the file size.
// The |available| argument indicates how much of the stream has been
// consumed. Returns negative on error, 0 on success.
virtual int Length(long long* total, long long* available);
// Read |len| bytes from the file stream into the cache, starting
// at absolute file position |pos|. This is a network simulator
// so the actual number of bytes read into the cache might be less
// than requested. Returns negative if error, 0 on success.
int PopulateCache(long long pos, long len);
// Notify this reader that the stream up to (but not including)
// offset |pos| has been parsed and is no longer of interest,
// hence that portion of the stream can be removed from the cache.
// Returns negative if error, 0 on success.
int PurgeCache(long long pos);
private:
MkvStream(const MkvStream&);
MkvStream& operator=(const MkvStream&);
struct Page {
int Read(MkvStream*);
enum { kSize = 1024 };
unsigned char buf_[kSize];
long long off_;
struct Less {
bool operator()(const Page& page, long long pos) const {
return (page.off_ < pos);
}
bool operator()(long long pos, const Page& page) const {
return (pos < page.off_);
}
bool operator()(const Page& lhs, const Page& rhs) const {
return (lhs.off_ < rhs.off_);
}
};
};
FILE* file_;
typedef std::deque<Page> cache_t;
cache_t cache_;
long long total_;
long long avail_;
};
#endif

42
netparse.cc Normal file
View File

@ -0,0 +1,42 @@
// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.
//#include <cstdio>
//#include <cstdlib>
#include "./mkvparser.hpp"
#include "./mkvstream.h"
namespace {
int ParserEbmlHeader(long long* pos);
}
int main(int argc, const char* argv[]) {
if (argc != 2) {
fprintf(stdout, "usage: netparse <mkvfile>\n");
return EXIT_SUCCESS;
}
MkvStream reader;
const char* const filename = argv[1];
int e = reader.Open(filename);
if (e) {
fprintf(stdout, "open failed\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
namespace {
int ParserEbmlHeader(long long* pos) {
}
}