mkvstream example - initial release
Change-Id: Iea0402764f62f47dc4e2c6e717867673acd043f3
This commit is contained in:
parent
4a5141344e
commit
dca6be3a32
259
mkvstream.cc
Normal file
259
mkvstream.cc
Normal 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
90
mkvstream.h
Normal 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
42
netparse.cc
Normal 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) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user