blob: d964d89afaa43f345eaa4fb713e86116eeb54a42 [file] [log] [blame]
// Copyright (c) 2010 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.
//
// Implements two reader subclasses: MkvReaderQT and MkvBufferedReaderQT
// that can be passed to the mkvparser object for its use in reading WebM data.
// See www.webmproject.org for more info.
//
#include "mkvreaderqt.hpp"
#include "log.h"
static void ReadCompletion(Ptr request, long refcon, OSErr readErr);
//-----------------------------------------------------------------------------
MkvReaderQT::MkvReaderQT()
: m_length(0), m_dataHandler(NULL), is_streaming_(false) {
}
//-----------------------------------------------------------------------------
MkvReaderQT::~MkvReaderQT() {
Close();
}
//-----------------------------------------------------------------------------
// Find and open an appropriate data handler for the given data
// reference and query its capabilities and optionally file size.
//
int MkvReaderQT::Open(Handle dataRef, OSType dataRefType, bool query_size) {
assert(!m_dataHandler);
if (dataRef == NULL)
return paramErr;
long fileSize = 0;
// Retrieve the best data handler component to use with the given
// data reference, for read purpoases. Then open the returned
// component using standard Component Manager calls.
OSType err;
ComponentInstance dataHandler = 0;
err = OpenAComponent(GetDataHandler(dataRef, dataRefType, kDataHCanRead),
&dataHandler);
if (err) return err;
// Provide a data reference to the data handler.
// Then you may start reading and/or writing
// movie data from that data reference.
err = DataHSetDataRef(dataHandler, dataRef);
if (err) return err;
// Open a read path to the current data reference.
// You need to do this before your component can read data using a
// data handler component.
err = DataHOpenForRead(dataHandler);
if (err) return err;
is_streaming_ = (dataRefType == URLDataHandlerSubType);
UInt32 flags;
err = DataHGetInfoFlags(dataHandler, &flags);
if (!err && (flags & kDataHInfoFlagNeverStreams))
is_streaming_ = false;
err = noErr;
m_length = -1;
if (query_size) {
// Get the size, in bytes, of the current data reference.
dbg_printf("[WebM Import] DataHGetFileSize ()...\n");
err = DataHGetFileSize(dataHandler, &fileSize);
if (!err)
m_length = fileSize;
dbg_printf("[WebM Import] file size = %ld (err: %ld)\n", fileSize, err);
}
m_dataHandler = dataHandler;
return noErr;
}
//-----------------------------------------------------------------------------
// Release the data handler component.
//
void MkvReaderQT::Close() {
if (m_dataHandler) {
CloseComponent(m_dataHandler);
m_dataHandler = NULL;
}
}
//-----------------------------------------------------------------------------
// Pass the read request to the data handler component, synchronously.
//
int MkvReaderQT::Read(long long position, long length, unsigned char* buffer) {
// sanity checks
if ((m_dataHandler == NULL) || (position < 0) || (length < 0) ||
(m_length >= 0 && position >= m_length))
return kInvalidArgsOrState;
if (length == 0)
return kSuccess;
if (length != 1)
dbg_printf("MkvReaderQT::Read() len = %ld\n", length);
// Schedule a synchronous read operation (no refcon, schdule record
// nor completion callback specified).
OSType err;
err = DataHScheduleData(m_dataHandler, (Ptr) buffer, position, length,
0, NULL, NULL);
if (err)
return kReadFailed;
return kSuccess;
}
//-----------------------------------------------------------------------------
// Return the file size (-1 if not known) as both total and available lengths.
//
int MkvReaderQT::Length(long long* total, long long* available) {
if (total)
*total = m_length;
if (available)
*available = m_length;
return kSuccess;
}
//-----------------------------------------------------------------------------
// Is our data actually a network stream?
//
bool MkvReaderQT::is_streaming() const {
return is_streaming_;
}
//-----------------------------------------------------------------------------
MkvBufferedReaderQT::MkvBufferedReaderQT()
: bufDataSize(0), bufStartFilePos(0), bufCurFilePos(0), bufEndFilePos(0),
m_PendingReadSize(0), chunk_size_(kReadChunkSize), eos_(false) {
bufDataMax = sizeof(buf);
}
//-----------------------------------------------------------------------------
MkvBufferedReaderQT::~MkvBufferedReaderQT() {
}
//-----------------------------------------------------------------------------
// In addition to base class's implementation, prepare a callback
// handle to use with asynchronous read requests.
//
int MkvBufferedReaderQT::Open(Handle dataRef, OSType dataRefType,
bool query_size) {
int status = MkvReaderQT::Open(dataRef, dataRefType, query_size);
if (!status)
read_completion_cb = NewDataHCompletionUPP(ReadCompletion);
return status;
}
//-----------------------------------------------------------------------------
// In addition to base class's implementation, release the callback
// handle.
//
void MkvBufferedReaderQT::Close() {
MkvReaderQT::Close();
if (read_completion_cb) {
DisposeDataHCompletionUPP(read_completion_cb);
read_completion_cb = NULL;
}
}
//-----------------------------------------------------------------------------
// Serve the read request if the internal buffer already contains the
// requested data. Return E_BUFFER_NOT_FULL otherwise.
//
int MkvBufferedReaderQT::Read(long long requestedPos, long requestedLen,
unsigned char* outbuf) {
if ((requestedPos < bufStartFilePos) ||
(requestedPos >= bufEndFilePos) ||
((requestedPos + requestedLen) > bufEndFilePos)) {
// Practically, shouldn't happen - we're accurately reporting the
// amount of available data and it never decreases...
return mkvparser::E_BUFFER_NOT_FULL;
}
// read from buffer
if (requestedLen == 1) {
outbuf[0] = buf[requestedPos - bufStartFilePos];
} else if (requestedLen > 1) {
memcpy(outbuf, buf + requestedPos - bufStartFilePos, requestedLen);
}
bufCurFilePos = requestedPos + requestedLen;
return kSuccess;
}
//-----------------------------------------------------------------------------
// Return the file size (-1 if not known) as the total length and the
// end of buffered data as the available length.
//
int MkvBufferedReaderQT::Length(long long* total, long long* available) {
if (total)
*total = m_length;
if (available)
*available = bufEndFilePos;
return kSuccess;
}
//-----------------------------------------------------------------------------
// Request an asynchronous read, of the given size, from the file into
// the internal buffer.
//
int MkvBufferedReaderQT::RequestFillBuffer(long request_size) {
if (m_PendingReadSize != 0) {
// The previous request is still pending - ignore the current one.
return 0;
}
dbg_printf("RequestFillBuffer...\n");
dbg_printf("MkvBufferedReaderQT::buf %ld [%ld - %ld - %ld] %ld\n",
bufDataSize, bufStartFilePos, bufCurFilePos, bufEndFilePos,
bufDataMax);
if (m_length >= 0 && (bufEndFilePos + request_size > m_length)) {
request_size = m_length - bufEndFilePos;
}
// if requested size wouldn't fit into available space in buffer,
// and currently consumed more than 3/4 of data in buffer, then
// compact the buffer.
if ((bufDataSize + request_size > bufDataMax) &&
((bufCurFilePos - bufStartFilePos) >
(bufEndFilePos - bufStartFilePos) * 0.75)) {
CompactBuffer(request_size);
}
if (bufDataSize + request_size > bufDataMax) {
// Still not enough available buffer space.
// Here we could resize the buffer if it was dynamically
// allocated. But since it's not - just indicate the error condition.
return kFillBufferNotEnoughSpace;
}
wide offset;
offset.hi = 0;
offset.lo = bufEndFilePos;
m_PendingReadSize = request_size;
readErr = DataHReadAsync(m_dataHandler, buf + bufDataSize, request_size,
&offset, read_completion_cb, (long) this);
dbg_printf("MkvBufferedReaderQT::RequestFillBuffer() scheduled %ld bytes"
" @ %ld, error=%d\n", request_size, bufEndFilePos, readErr);
if (readErr)
m_PendingReadSize = 0;
return readErr;
}
//-----------------------------------------------------------------------------
// Same as above, just with a previously set request size.
int MkvBufferedReaderQT::RequestFillBuffer() {
return RequestFillBuffer(chunk_size_);
}
//-----------------------------------------------------------------------------
// Free some space in the buffer by removing part of the already
// consumed data.
//
void MkvBufferedReaderQT::CompactBuffer(long requestedSize) {
long remainingDataSize = (bufDataMax - bufDataSize);
long consumedDataSize = (bufCurFilePos - bufStartFilePos);
long killDataSize =
(consumedDataSize > kReadChunkSize) ?
(consumedDataSize - kReadChunkSize) : (consumedDataSize * 0.50);
if (requestedSize > remainingDataSize) {
if (killDataSize > (requestedSize - remainingDataSize)) {
dbg_printf("CompactBuffer() consumedDataSize=%ld, killDataSize=%ld\n",
consumedDataSize, killDataSize);
memmove(&buf[0], &buf[killDataSize], (bufDataSize - killDataSize));
bufDataSize -= killDataSize;
bufStartFilePos += killDataSize;
} else {
dbg_printf("CompactBuffer() FAIL. Wait for more data to be consumed.\n");
}
} else {
dbg_printf("CompactBuffer() NOP, don't need to compact at this time.\n");
}
}
//-----------------------------------------------------------------------------
// Give the data handler some CPU time to handle the asynchronous requests.
//
void MkvBufferedReaderQT::TaskDataHandler() {
DataHTask(m_dataHandler);
}
//-----------------------------------------------------------------------------
// Set the default read chunk size if it doesn't exceed the set maximum.
//
long MkvBufferedReaderQT::set_chunk_size(long size) {
chunk_size_ = (size < kMaxReadChunkSize) ? size : kMaxReadChunkSize;
return chunk_size_;
}
long MkvBufferedReaderQT::chunk_size() const {
return chunk_size_;
}
//-----------------------------------------------------------------------------
// Is an asynchronous fill buffer request already pending?
//
bool MkvBufferedReaderQT::RequestPending() const {
return (m_PendingReadSize != 0);
}
//-----------------------------------------------------------------------------
// Has the end of data been reached?
//
bool MkvBufferedReaderQT::eos() const {
return eos_ || bufEndFilePos == m_length;
}
//-----------------------------------------------------------------------------
// Handle one completed asynchronous read request.
//
void MkvBufferedReaderQT::ReadCompleted(Ptr request, OSErr read_error) {
if (read_error) {
if (read_error == eofErr)
eos_ = true;
else
readErr = read_error;
} else {
bufEndFilePos += m_PendingReadSize; // incr file position by amount
// that was just read into buf
bufDataSize += m_PendingReadSize; // incr size of data in buf
}
m_PendingReadSize = 0;
dbg_printf("...ReadCompletion (filePos=%ld) [ptr=%p] = %d\n", bufEndFilePos,
request, read_error);
}
//-----------------------------------------------------------------------------
// C -> C++ wrapper to be able to pass callback to QuickTime data
// handler on asynchronous read requests.
//
static void ReadCompletion(Ptr request, long refcon, OSErr read_error) {
MkvBufferedReaderQT* reader =
reinterpret_cast<MkvBufferedReaderQT*>(refcon);
reader->ReadCompleted(request, read_error);
}