blob: 98b7d4ee7bc86c0179223afe100fd8992dddad9e [file]
// 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.
#pragma warning(disable:4702) //unreachable code
#include <strmif.h>
#include "mkvreader.h"
#include <cassert>
#include <algorithm>
#include <vfwmsgs.h>
#include "clockable.h"
#pragma warning(default:4702)
namespace WebmSplit
{
MkvReader::MkvReader() : m_sync_read(true)
{
}
MkvReader::~MkvReader()
{
}
HRESULT MkvReader::SetSource(IAsyncReader* pSource)
{
HRESULT hr;
if (pSource == 0)
{
hr = Decommit();
assert(SUCCEEDED(hr));
m_pSource = 0;
m_pAllocator = 0;
return S_OK;
}
#if 0 //def _DEBUG
LONGLONG avail;
hr = pSource->Length(&m_total, &avail);
assert(SUCCEEDED(hr));
assert(avail <= m_total);
m_avail = 128 * 1024;
if (m_avail > m_total)
m_avail = m_total;
#endif
// Get page size.
SYSTEM_INFO info;
GetSystemInfo(&info);
const int kPageSize = info.dwPageSize;
// The splitter is at the mercy of the filter graph manager, and is unable
// to control how much data it will be asked to store. Configure the
// allocator to support storage of up to 1 second of data at 5 MB/sec.
const int k5MBPS = 5 * 1000 * 1000;
const int kNumBuffers = k5MBPS / kPageSize;
ALLOCATOR_PROPERTIES props = {0};
props.cBuffers = kNumBuffers;
props.cbBuffer = kPageSize;
hr = pSource->RequestAllocator(0, &props, &m_pAllocator);
if (FAILED(hr))
return hr;
hr = m_pAllocator->GetProperties(&m_props);
assert(SUCCEEDED(hr));
assert(m_props.cBuffers > 0);
assert(m_props.cbBuffer > 0);
if (FAILED(hr) || m_props.cBuffers < 1 || m_props.cbBuffer < 1)
return VFW_E_NO_ALLOCATOR;
m_pSource = pSource;
hr = Commit();
assert(SUCCEEDED(hr));
return hr;
}
bool MkvReader::IsOpen() const
{
return m_pSource;
}
HRESULT MkvReader::Commit()
{
//This is called by the FGM thread, during the transition to Run/Paused
//from Stopped, but before any other threads have been created. Therefore
//no thread synchronization is performed.
assert(m_pages.empty());
assert(m_free_pages.empty());
if (m_pAllocator == 0)
return VFW_E_NO_ALLOCATOR;
const HRESULT hr = m_pAllocator->Commit();
if (FAILED(hr))
return hr;
const long n = m_props.cBuffers;
assert(n > 0);
for (long i = 0; i < n; ++i)
{
Page page;
page.cRef = 0;
page.pSample = 0;
m_pages.push_back(page);
}
typedef pages_list_t::iterator iter_t;
iter_t iter = m_pages.begin();
const iter_t iter_end = m_pages.end();
while (iter != iter_end)
{
const LONGLONG key = iter->GetPos();
const free_pages_t::value_type value(key, iter);
m_free_pages.insert(value);
++iter;
}
return S_OK;
}
HRESULT MkvReader::Decommit()
{
//This is called by the FGM thread, during the transition from Run/Paused
//to stopped, but after any other threads have been destroyed. Therefore
//no thread synchronization is performed.
m_free_pages.clear();
while (!m_pages.empty())
{
Page& page = m_pages.front();
assert(page.cRef == 0);
if (page.pSample)
{
const ULONG n = page.pSample->Release();
n;
page.pSample = 0;
}
m_pages.pop_front();
}
if (m_pAllocator == 0)
return S_OK;
return m_pAllocator->Decommit();
}
int MkvReader::Read(
long long pos,
long len,
unsigned char* buf)
{
if (!IsOpen())
return -1;
if (m_sync_read)
{
const HRESULT hr = m_pSource->SyncRead(pos, len, buf);
return SUCCEEDED(hr) ? 0 : -1;
}
if (pos < 0)
return -1;
if (len <= 0)
return 0;
if (buf == 0)
return -1;
typedef cache_t::iterator iter_t;
iter_t next;
const DWORD page_size = m_props.cbBuffer;
if (m_cache.empty() || ((*m_cache.front()).GetPos() > pos))
next = m_cache.begin();
else
{
const iter_t i = m_cache.begin();
const iter_t j = m_cache.end();
next = std::upper_bound(i, j, pos, PageLess());
assert(next != i);
const cache_t::value_type page_iter = *--iter_t(next);
const Page& page = *page_iter;
const LONGLONG page_end = page.GetPos() + page_size;
if (pos < page_end) //cache hit
Read(page_iter, pos, len, &buf);
}
while (len > 0)
{
if ((next == m_cache.end()) || ((*next)->GetPos() > pos))
{
iter_t curr_iter;
const int status = InsertPage(next, pos, curr_iter);
if (status < 0) //error
return status;
const cache_t::value_type page_iter = *curr_iter;
const Page& curr_page = *page_iter;
next = ++iter_t(curr_iter);
assert((next == m_cache.end()) ||
((*next)->GetPos() > curr_page.GetPos()));
Read(page_iter, pos, len, &buf);
}
else
{
const cache_t::value_type page_iter = *next++;
const Page& page = *page_iter;
assert(page.GetPos() <= pos);
assert(pos < (page.GetPos() + page_size));
Read(page_iter, pos, len, &buf);
}
}
return 0; //means all requested bytes were read
}
void MkvReader::Read(
pages_list_t::const_iterator page_iter,
long long& pos,
long& requested_len,
unsigned char** pdst) const
{
const Page& page = *page_iter;
const LONGLONG page_pos = page.GetPos();
assert(pos >= page_pos);
const LONG page_size = m_props.cbBuffer;
const LONGLONG off_ = pos - page_pos;
assert(off_ >= 0);
assert(off_ <= LONG_MAX);
const LONG page_off = static_cast<LONG>(off_); //within page
assert(page_off < page_size);
const LONG page_len = page_size - page_off; //what remains on page
const long len = (requested_len <= page_len) ? requested_len : page_len;
if (pdst)
{
assert(page.pSample);
BYTE* ptr;
const HRESULT hr = page.pSample->GetPointer(&ptr);
assert(SUCCEEDED(hr));
assert(ptr);
const BYTE* const page_base = ptr;
const BYTE* const src = page_base + page_off;
unsigned char*& dst = *pdst;
memcpy(dst, src, len);
dst += len;
}
pos += len;
requested_len -= len;
}
int MkvReader::InsertPage(
cache_t::iterator next,
LONGLONG pos,
cache_t::iterator& cache_iter)
{
FreeOne(next);
if (m_free_pages.empty()) //error: all samples are busy
return -1; //generic error
const DWORD page_size = m_props.cbBuffer;
const LONGLONG page_pos = page_size * LONGLONG(pos / page_size);
assert((next == m_cache.end()) || ((*next)->GetPos() > page_pos));
free_pages_t::iterator free_page = m_free_pages.find(page_pos);
if (free_page == m_free_pages.end())
free_page = m_free_pages.begin();
const pages_list_t::iterator page_iter = free_page->second;
assert(page_iter->cRef == 0);
if (page_iter->GetPos() == page_pos)
{
m_free_pages.erase(free_page);
cache_iter = m_cache.insert(next, page_iter);
return 0; //success
}
LONGLONG total, available;
const int status = Length(&total, &available);
if (status < 0)
return status;
assert(available <= total);
assert(page_pos < total);
const LONGLONG page_end = page_pos + page_size;
//TODO: there is a problem here. The caller probably checked
//already whether the attempted read would be past available,
//and if not the he would call Read assuming it would not
//fail. However, we make a stronger test here, because we
//test the position of the end of the page -- but this pos
//is most likely beyond the pos tested by caller, so we
//return E_BUFFER_NOT_FULL when the caller is not expecting
//it. We could just go ahead and read the entire page here,
//but then we run the risk of a "long delay" because we
//attempt a read beyong the available value. One alternative
//is for the caller to attempt to read the byte just beyond
//his intended range. Yet another possibility is to read
//up to available (instead of requiring page_end), and try
//to manage the fact that the page isn't full. But that
//won't work either, because reads must be aligned (you
//must request and entire page).
if ((page_end <= total) && (page_end > available))
return mkvparser::E_BUFFER_NOT_FULL;
HRESULT hr;
Page& page = *page_iter;
if (page.pSample == 0)
{
hr = m_pAllocator->GetBuffer(&page.pSample, 0, 0, 0);
assert(SUCCEEDED(hr));
assert(page.pSample);
}
LONGLONG st = page_pos * 10000000;
LONGLONG sp = page_end * 10000000;
hr = page.pSample->SetTime(&st, &sp);
assert(SUCCEEDED(hr));
hr = m_pSource->SyncReadAligned(page.pSample);
if (FAILED(hr)) //VFW_S_WRONG_STATE
{
const ULONG cRef = page.pSample->Release();
cRef;
page.pSample = 0;
return -1; //generic error value
}
m_free_pages.erase(free_page);
cache_iter = m_cache.insert(next, page_iter);
return 0; //success
}
int MkvReader::Length(
long long* pTotal,
long long* pAvailable)
{
if (!IsOpen())
return -1;
#if 0 //def _DEBUG
assert(m_total >= 0);
assert(m_avail <= m_total);
if (m_avail < m_total)
{
m_avail += 1024;
if (m_avail > m_total)
m_avail = m_total;
}
*pTotal = m_total;
*pAvailable = m_avail;
return 0;
#else
const HRESULT hr = m_pSource->Length(pTotal, pAvailable);
if (FAILED(hr))
return -1;
return 0;
#endif
}
void MkvReader::PurgeOne()
{
if (m_cache.empty())
return;
{
const cache_t::value_type page_iter = m_cache.front();
const Page& page = *page_iter;
if (page.cRef <= 0)
{
m_cache.pop_front();
const free_pages_t::value_type value(page.GetPos(), page_iter);
m_free_pages.insert(value);
return;
}
}
{
const cache_t::value_type page_iter = m_cache.back();
const Page& page = *page_iter;
if (page.cRef <= 0)
{
m_cache.pop_back();
const free_pages_t::value_type value(page.GetPos(), page_iter);
m_free_pages.insert(value);
return;
}
}
{
typedef cache_t::iterator iter_t;
iter_t cache_iter = --iter_t(m_cache.end());
for (;;)
{
const cache_t::value_type page_iter = *cache_iter;
const Page& page = *page_iter;
if (page.cRef == 0)
{
m_cache.erase(cache_iter);
const free_pages_t::value_type value(page.GetPos(), page_iter);
m_free_pages.insert(value);
return;
}
if (cache_iter == m_cache.begin())
break;
--cache_iter;
}
return;
}
}
LONGLONG MkvReader::Page::GetPos() const
{
if (pSample == 0)
return -1;
LONGLONG st, sp;
const HRESULT hr = pSample->GetTime(&st, &sp);
assert(SUCCEEDED(hr));
assert(st >= 0);
assert((st % 10000000) == 0);
const LONGLONG pos = st / 10000000;
return pos;
}
HRESULT MkvReader::Wait(
CLockable& lock,
LONGLONG start_pos,
LONG size,
DWORD timeout)
{
assert(start_pos >= 0);
assert(size > 0);
//lock has already been seized
cache_t::iterator next = m_cache.end();
FreeOne(next);
if (m_free_pages.empty()) //all samples are busy
return E_FAIL;
free_pages_t::iterator free_page = m_free_pages.begin();
pages_list_t::iterator page_iter = free_page->second;
m_free_pages.erase(free_page);
//We now own this page.
Page& page = *page_iter;
assert(page.cRef == 0);
HRESULT hr;
if (page.pSample == 0)
{
hr = m_pAllocator->GetBuffer(&page.pSample, 0, 0, 0);
assert(SUCCEEDED(hr));
assert(page.pSample);
}
#if 0
LONGLONG total, avail;
const int status = Length(&total, &avail);
assert(status == 0);
assert(avail <= total);
#endif
const LONGLONG stop_pos = start_pos + LONGLONG(size) - 1; //last byte
const DWORD page_size = m_props.cbBuffer;
const LONGLONG page_pos = page_size * LONGLONG(stop_pos / page_size);
LONGLONG st = page_pos * 10000000;
LONGLONG sp = (page_pos + page_size) * 10000000;
hr = page.pSample->SetTime(&st, &sp);
assert(SUCCEEDED(hr));
assert(page.GetPos() == page_pos);
hr = m_pSource->Request(page.pSample, 0);
HRESULT hrWait;
if (SUCCEEDED(hr))
{
IMediaSample* pSample;
DWORD_PTR token;
hr = lock.Release();
assert(SUCCEEDED(hr));
hrWait = m_pSource->WaitForNext(timeout, &pSample, &token);
hr = lock.Seize(INFINITE);
assert(SUCCEEDED(hr));
if (SUCCEEDED(hrWait))
{
assert(pSample == page.pSample);
assert(token == 0);
pSample = 0;
const free_pages_t::value_type value(page_pos, page_iter);
m_free_pages.insert(value);
#if 0 //def _DEBUG
const LONGLONG avail = page_pos + page_size;
m_avail = (avail >= m_total) ? m_total : avail;
#endif
return S_OK;
}
hrWait = E_FAIL;
}
//async read request failed, or was cancelled
IMediaSample* pSample;
DWORD_PTR token;
for (;;)
{
hrWait = m_pSource->WaitForNext(0, &pSample, &token);
if (pSample == 0)
break;
}
const ULONG cRef = page.pSample->Release();
cRef;
page.pSample = 0;
assert(page.GetPos() < 0);
const free_pages_t::value_type value(-1, page_iter);
m_free_pages.insert(value);
return VFW_E_TIMEOUT;
}
HRESULT MkvReader::BeginFlush()
{
return m_pSource->BeginFlush();
}
HRESULT MkvReader::EndFlush()
{
return m_pSource->EndFlush();
}
void MkvReader::FreeOne(cache_t::iterator& next)
{
if (!m_free_pages.empty())
return;
LONGLONG next_pos;
if (next == m_cache.end())
next_pos = -1;
else
{
const cache_t::value_type page_iter = *next;
Page& page = *page_iter;
next_pos = page.GetPos();
assert(next_pos >= 0);
++page.cRef;
}
PurgeOne();
if (!m_free_pages.empty()) //success
{
if (next_pos < 0)
next = m_cache.end();
else
{
typedef cache_t::iterator iter_t;
const iter_t i = m_cache.begin();
const iter_t j = m_cache.end();
next = std::lower_bound(i, j, next_pos, PageLess());
assert(next != j);
const cache_t::value_type page_iter = *next;
const Page& page = *page_iter;
page;
assert(page.GetPos() == next_pos);
assert(page.cRef > 0);
}
}
if (next_pos >= 0)
{
assert(next != m_cache.end());
const cache_t::value_type page_iter = *next;
Page& page = *page_iter;
assert(page.GetPos() == next_pos);
assert(page.cRef > 0);
--page.cRef;
}
}
HRESULT MkvReader::LockPages(const mkvparser::BlockEntry* pBE)
{
if (pBE == 0)
return S_FALSE;
if (pBE->EOS())
return S_FALSE;
const mkvparser::Block* const pBlock = pBE->GetBlock();
assert(pBlock);
LONGLONG pos = pBlock->m_start;
long len = static_cast<long>(pBlock->m_size);
typedef cache_t::iterator iter_t;
iter_t next;
const DWORD page_size = m_props.cbBuffer;
// int n = 0;
if (m_cache.empty() || (pos < (*m_cache.front()).GetPos()))
next = m_cache.begin();
else
{
const iter_t i = m_cache.begin();
const iter_t j = m_cache.end();
next = std::upper_bound(i, j, pos, PageLess());
assert(next != i);
const cache_t::value_type page_iter = *--iter_t(next);
Page& page = *page_iter;
const LONGLONG page_end = page.GetPos() + page_size;
if (pos < page_end) //cache hit
{
Read(page_iter, pos, len, 0);
++page.cRef;
// ++n;
}
}
while (len > 0)
{
if ((next == m_cache.end()) || ((*next)->GetPos() > pos))
{
iter_t curr_iter;
const int status = InsertPage(next, pos, curr_iter);
if (status < 0) //error
return status;
if (curr_iter == m_cache.end()) //async read is req'd
return VFW_E_BUFFER_UNDERFLOW;
const cache_t::value_type page_iter = *curr_iter;
Page& page = *page_iter;
next = ++iter_t(curr_iter);
assert((next == m_cache.end()) ||
((*next)->GetPos() > page.GetPos()));
Read(page_iter, pos, len, 0);
++page.cRef;
// ++n;
}
else
{
const cache_t::value_type page_iter = *next++;
Page& page = *page_iter;
assert(page.GetPos() <= pos);
assert(pos < (page.GetPos() + page_size));
Read(page_iter, pos, len, 0);
++page.cRef;
// ++n;
}
}
return S_OK;
}
void MkvReader::UnlockPages(const mkvparser::BlockEntry* pBE)
{
if (pBE == 0)
return;
if (pBE->EOS())
return;
const mkvparser::Block* const pBlock = pBE->GetBlock();
assert(pBlock);
LONGLONG pos = pBlock->m_start;
long len = static_cast<long>(pBlock->m_size);
const DWORD page_size = m_props.cbBuffer;
assert(!m_cache.empty());
assert((*m_cache.front()).GetPos() <= pos);
typedef cache_t::iterator iter_t;
const iter_t i = m_cache.begin();
const iter_t j = m_cache.end();
iter_t next = std::upper_bound(i, j, pos, PageLess());
assert(next != i);
{
const cache_t::value_type page_iter = *--iter_t(next);
Page& page = *page_iter;
const LONGLONG page_end = page.GetPos() + page_size;
if (pos < page_end) //cache hit
{
assert(page.cRef > 0);
Read(page_iter, pos, len, 0);
--page.cRef;
}
}
while (len > 0)
{
assert(next != m_cache.end());
assert((*next)->GetPos() <= pos);
const cache_t::value_type page_iter = *next++;
Page& page = *page_iter;
assert(page.GetPos() <= pos);
assert(pos < (page.GetPos() + page_size));
assert(page.cRef > 0);
Read(page_iter, pos, len, 0);
--page.cRef;
}
}
} //end namespace WebmSplit