blob: e1b9b226136542d726c6387fc898c28a1cbbf85c [file]
#include <strmif.h>
#include "oggtrackaudio.hpp"
#include "vorbistypes.hpp"
#include "cmediatypes.hpp"
#include <new>
#include <cassert>
#include <uuids.h>
#if 0 //def _DEBUG
#include "odbgstream.hpp"
#include <iomanip>
using std::endl;
using std::fixed;
using std::setprecision;
#endif
using namespace oggparser;
//enum { cBuffers = 1024 };
//enum { cbBuffer = 1024 };
namespace WebmOggSource
{
HRESULT OggTrackAudio::Create(
oggparser::OggStream* pStream,
OggTrackAudio*& pTrack)
{
if (pStream == 0)
return E_INVALIDARG;
pTrack = new (std::nothrow) OggTrackAudio(pStream, 1);
if (pTrack == 0)
return E_OUTOFMEMORY;
HRESULT hr = pTrack->Init();
if (SUCCEEDED(hr))
return hr;
delete pTrack;
pTrack = 0;
return hr;
}
OggTrackAudio::OggTrackAudio(
oggparser::OggStream* pStream,
ULONG id) :
OggTrack(pStream, id),
m_granule_pos(0),
m_reftime(0),
m_pfnGetSampleCount(0),
m_pfnPopulateSamples(0)
{
}
OggTrackAudio::~OggTrackAudio()
{
}
HRESULT OggTrackAudio::Init()
{
long result = m_pStream->Init(m_ident, m_comment, m_setup);
if (result < 0) //error
return E_FAIL; //TODO: refine this return value
result = m_fmt.Read(m_pStream->m_pReader, m_ident);
if (result < 0)
return E_FAIL;
return S_OK;
}
void OggTrackAudio::OnReset()
{
m_granule_pos = 0;
m_reftime = 0;
}
void OggTrackAudio::GetMediaTypes(CMediaTypes& mtv) const
{
IOggReader* const pReader = m_pStream->m_pReader;
const long ident_len = m_ident.GetLength();
assert(ident_len == 30);
const long comment_len = m_comment.GetLength();
assert(comment_len >= 7);
const long setup_len = m_setup.GetLength();
assert(setup_len >= 7);
const size_t hdr_len = ident_len + comment_len + setup_len;
using VorbisTypes::VORBISFORMAT2;
const size_t cb = sizeof(VORBISFORMAT2) + hdr_len;
BYTE* const pb = (BYTE*)_malloca(cb);
VORBISFORMAT2& fmt = (VORBISFORMAT2&)(*pb);
fmt.channels = m_fmt.channels;
fmt.samplesPerSec = m_fmt.sample_rate;
fmt.bitsPerSample = 0; //TODO: how do we synthesize this value?
fmt.headerSize[0] = ident_len;
fmt.headerSize[1] = comment_len;
fmt.headerSize[2] = setup_len;
BYTE* const dst_end = pb + cb;
BYTE* dst = pb + sizeof(VORBISFORMAT2);
assert(dst < dst_end);
long result = m_ident.Copy(pReader, dst);
assert(result == ident_len);
dst += ident_len;
assert(dst < dst_end);
result = m_comment.Copy(pReader, dst);
assert(result == comment_len);
dst += comment_len;
assert(dst < dst_end);
result = m_setup.Copy(pReader, dst);
assert(result == setup_len);
dst += setup_len;
assert(dst == dst_end);
AM_MEDIA_TYPE mt;
mt.majortype = MEDIATYPE_Audio;
mt.subtype = VorbisTypes::MEDIASUBTYPE_Vorbis2;
mt.bFixedSizeSamples = FALSE;
mt.bTemporalCompression = FALSE;
mt.lSampleSize = 0;
mt.formattype = VorbisTypes::FORMAT_Vorbis2;
mt.pUnk = 0;
mt.cbFormat = static_cast<ULONG>(cb);
mt.pbFormat = pb;
mtv.Add(mt);
mt.subtype = VorbisTypes::MEDIASUBTYPE_Vorbis2_Xiph_Lacing;
mtv.Add(mt);
}
HRESULT OggTrackAudio::QueryAccept(const AM_MEDIA_TYPE* pmt) const
{
if (pmt == 0)
return E_INVALIDARG;
const AM_MEDIA_TYPE& mt = *pmt;
if (mt.majortype != MEDIATYPE_Audio)
return S_FALSE;
if (mt.subtype == VorbisTypes::MEDIASUBTYPE_Vorbis2)
__noop;
else if (mt.subtype == VorbisTypes::MEDIASUBTYPE_Vorbis2_Xiph_Lacing)
__noop;
else
return S_FALSE;
if (mt.formattype == GUID_NULL)
return S_OK; //for now, just ignore remaining items
if (mt.formattype != VorbisTypes::FORMAT_Vorbis2)
return S_FALSE;
if (mt.pbFormat == 0)
return S_FALSE;
using VorbisTypes::VORBISFORMAT2;
if (mt.cbFormat < sizeof(VORBISFORMAT2))
return S_FALSE;
const VORBISFORMAT2& fmt = (VORBISFORMAT2&)(*mt.pbFormat);
if (fmt.channels != m_fmt.channels)
return S_FALSE;
if (fmt.samplesPerSec != m_fmt.sample_rate)
return S_FALSE;
//fmt.bitsPerSample = 0; //TODO: how do we synthesize this value?
const DWORD ident_len = fmt.headerSize[0];
const DWORD comment_len = fmt.headerSize[1];
const DWORD setup_len = fmt.headerSize[2];
const DWORD hdr_len = ident_len + comment_len + setup_len;
const DWORD cb = sizeof(VORBISFORMAT2) + hdr_len;
if (mt.cbFormat < cb)
return S_FALSE;
//TODO: vet hdrs
return S_OK;
}
HRESULT OggTrackAudio::SetConnectionMediaType(const AM_MEDIA_TYPE& mt)
{
if (mt.subtype == VorbisTypes::MEDIASUBTYPE_Vorbis2)
{
m_pfnGetSampleCount = &OggTrackAudio::GetSampleCountVorbis2;
m_pfnPopulateSamples = &OggTrackAudio::PopulateSamplesVorbis2;
}
else if (mt.subtype == VorbisTypes::MEDIASUBTYPE_Vorbis2_Xiph_Lacing)
{
m_pfnGetSampleCount =
&OggTrackAudio::GetSampleCountVorbis2XiphLacing;
m_pfnPopulateSamples =
&OggTrackAudio::PopulateSamplesVorbis2XiphLacing;
}
else
return E_FAIL;
m_subtype = mt.subtype;
return S_OK;
}
HRESULT OggTrackAudio::UpdateAllocatorProperties(
ALLOCATOR_PROPERTIES& props) const
{
long cBuffers;
long cbBuffer;
if (m_subtype == VorbisTypes::MEDIASUBTYPE_Vorbis2)
{
cBuffers = 256;
cbBuffer = 8 * 1024; //TODO: synthesize a better estimate
}
else
{
assert(m_subtype == VorbisTypes::MEDIASUBTYPE_Vorbis2_Xiph_Lacing);
cBuffers = 1;
cbBuffer = 64 * 1024;
}
if (props.cBuffers < cBuffers)
props.cBuffers = cBuffers;
if (props.cbBuffer < cbBuffer)
props.cbBuffer = cbBuffer;
if (props.cbAlign <= 0)
props.cbAlign = 1;
if (props.cbPrefix < 0)
props.cbPrefix = 0;
return S_OK;
}
//long OggTrackAudio::GetBufferSize() const
//{
// const long blocks_per_frame = m_fmt.blocksize_1; //?
// const long bytes_per_block = sizeof(float) * m_fmt.channels;
// const long bytes_per_frame = blocks_per_frame * bytes_per_block;
//
// return bytes_per_frame;
//}
std::wostream& OggTrackAudio::GetKind(std::wostream& os) const
{
return os << L"Audio";
}
std::wstring OggTrackAudio::GetCodecName() const
{
return L"Vorbis";
}
#if 0
HRESULT OggTrackAudio::GetPackets(long& count)
{
const long result = GetPackets();
if (result < 0)
return E_FAIL;
if (m_packets.empty())
return S_FALSE;
count = (this->*m_pfnGetSampleCount)();
if (count <= 0)
return E_FAIL;
return S_OK;
}
long OggTrackAudio::GetPackets()
{
//m_granule_pos represents the trailing edge of the
//audio we have already consumed. It is therefore
//the leading edge of the sample(s) we are about
//to push downstream.
//We need to consume packets until we get (at least)
//one that has a non-negative granule pos -- that
//packet is the last amond the packets we send downstream;
//Its granule pos is the trailing edge of the current
//media sample we're about to send, and that value
//then becomes the leading edge of the next media sample.
//If there are intervening packets, then we can either
//estimate the granule pos of each one (perhaps ident.blocksize_{0|1}
//can be used for this), or we can just pass packets downstream
//without a media time and hope for the best.
if (m_granule_pos < 0)
return -1;
if (!m_packets.empty()) //weird
{
const OggStream::Packet& pkt = m_packets.back();
if (pkt.granule_pos >= 0)
{
assert(pkt.granule_pos > m_granule_pos);
return 0; //success
}
}
for (;;)
{
OggStream::Packet pkt;
const long result = m_pStream->GetPacket(pkt);
if (result < 0) //error (or EOF)
{
if (result != oggparser::E_END_OF_FILE)
return result;
if (m_packets.empty())
return 0;
if (m_packets.back().granule_pos < 0)
return -1;
return 0; //done
}
m_packets.push_back(pkt);
if (pkt.granule_pos >= 0)
{
assert(pkt.granule_pos > m_granule_pos);
return 0; //success
}
}
}
#else
HRESULT OggTrackAudio::GetPackets(long& count)
{
if (!m_packets.empty()) //weird
{
const OggStream::Packet& pkt = m_packets.back();
if (pkt.granule_pos >= 0)
{
assert(m_granule_pos >= 0);
assert(pkt.granule_pos > m_granule_pos);
count = (this->*m_pfnGetSampleCount)();
assert(count > 0);
return S_OK;
}
}
for (;;)
{
OggStream::Packet pkt;
const long result = m_pStream->GetPacket(pkt);
if (result < 0) //error (or EOF)
{
if (result != oggparser::E_END_OF_FILE)
return E_FAIL;
break;
}
m_packets.push_back(pkt);
if (pkt.granule_pos >= 0)
{
assert(m_granule_pos >= 0);
assert(pkt.granule_pos > m_granule_pos);
count = (this->*m_pfnGetSampleCount)();
assert(count > 0);
return S_OK;
}
}
//no more packets in stream
if (m_packets.empty())
return S_FALSE; //EOS
const OggStream::Packet& back = m_packets.back();
if (back.granule_pos < 0)
return E_FAIL;
assert(m_granule_pos >= 0);
assert(back.granule_pos > m_granule_pos);
count = (this->*m_pfnGetSampleCount)();
assert(count > 0);
return S_OK;
}
#endif
long OggTrackAudio::GetSampleCountVorbis2() const
{
const packets_t::size_type size = m_packets.size();
return static_cast<long>(size);
}
long OggTrackAudio::GetSampleCountVorbis2XiphLacing() const
{
return 1;
}
HRESULT OggTrackAudio::PopulateSamples(const samples_t& ss)
{
return (this->*m_pfnPopulateSamples)(ss);
}
HRESULT OggTrackAudio::PopulateSamplesVorbis2(const samples_t& ss)
{
if (m_packets.empty())
return E_FAIL;
const packets_t::size_type packet_count = m_packets.size();
if (ss.size() != packet_count)
return E_FAIL;
if (m_granule_pos < 0)
return E_FAIL;
if (m_reftime < 0)
return E_FAIL;
const LONGLONG granule_pos = m_packets.back().granule_pos;
if (granule_pos < 0)
return E_FAIL;
if (granule_pos <= m_granule_pos)
return E_FAIL;
float samples_per_packet;
if (granule_pos < 0) //EOS, and no terminating granule_pos
samples_per_packet = 1024; //?
else
{
const LONGLONG total_samples = granule_pos - m_granule_pos;
samples_per_packet = float(total_samples) / packet_count;
}
const float samples_per_sec = static_cast<float>(m_fmt.sample_rate);
LONGLONG curr_samples = m_granule_pos;
float curr_samples_ = static_cast<float>(curr_samples);
LONGLONG curr_reftime = m_reftime;
samples_t::const_iterator tgt_iter = ss.begin();
while (m_packets.size() > 1)
{
const OggStream::Packet& pkt = m_packets.front();
IMediaSample* const pSample = *tgt_iter++;
assert(pSample);
HRESULT hr = PopulateSample(pkt, pSample);
if (FAILED(hr))
return hr;
m_bDiscontinuity = false;
const float next_samples_ = curr_samples_ + samples_per_packet;
LONGLONG next_samples = static_cast<LONGLONG>(next_samples_);
const float next_sec = next_samples_ / samples_per_sec;
const float next_reftime_ = next_sec * 10000000.0F;
LONGLONG next_reftime = static_cast<LONGLONG>(next_reftime_);
hr = pSample->SetMediaTime(&curr_samples, &next_samples);
assert(SUCCEEDED(hr));
hr = pSample->SetTime(&curr_reftime, &next_reftime);
assert(SUCCEEDED(hr));
curr_samples = next_samples;
curr_samples_ = next_samples_;
curr_reftime = next_reftime;
m_packets.pop_front();
}
{
const OggStream::Packet& pkt = m_packets.front();
IMediaSample* const pSample = *tgt_iter++;
assert(pSample);
assert(tgt_iter == ss.end());
HRESULT hr = PopulateSample(pkt, pSample);
if (FAILED(hr))
return hr;
m_bDiscontinuity = false;
if (granule_pos < 0) //EOS, and no granule_pos on page
{
m_granule_pos = -1; //EOS
m_reftime = -1;
hr = pSample->SetMediaTime(0, 0);
assert(SUCCEEDED(hr));
hr = pSample->SetTime(&curr_reftime, 0);
assert(SUCCEEDED(hr));
}
else
{
m_granule_pos = granule_pos; //next_samples
const float next_samples_ = static_cast<float>(m_granule_pos);
const float next_sec = next_samples_ / samples_per_sec;
const float next_reftime_ = next_sec * 10000000.0F;
m_reftime = static_cast<LONGLONG>(next_reftime_); //next_reftime
hr = pSample->SetMediaTime(&curr_samples, &m_granule_pos);
assert(SUCCEEDED(hr));
hr = pSample->SetTime(&curr_reftime, &m_reftime);
assert(SUCCEEDED(hr));
}
m_packets.pop_front();
assert(m_packets.empty());
}
return S_OK;
}
HRESULT OggTrackAudio::PopulateSample(
const OggStream::Packet& pkt,
IMediaSample* pSample) const
{
assert(pSample);
const long len = pkt.GetLength();
assert(len > 0);
BYTE* ptr;
HRESULT hr = pSample->GetPointer(&ptr);
assert(SUCCEEDED(hr));
assert(ptr);
const long size = pSample->GetSize();
size;
assert(size >= len);
const long result = pkt.Copy(m_pStream->m_pReader, ptr);
result;
assert(result == len);
assert((*ptr & 0x01) == 0);
hr = pSample->SetActualDataLength(len);
assert(SUCCEEDED(hr));
hr = pSample->SetDiscontinuity(m_bDiscontinuity ? TRUE : FALSE);
assert(SUCCEEDED(hr));
//m_bDiscontinuity = false;
hr = pSample->SetSyncPoint(TRUE);
assert(SUCCEEDED(hr));
hr = pSample->SetPreroll(FALSE);
assert(SUCCEEDED(hr));
//hr = pSample->SetMediaTime(&t.curr_samples, &t.next_samples);
//assert(SUCCEEDED(hr));
//hr = pSample->SetTime(&t.curr_reftime, &t.next_reftime);
//assert(SUCCEEDED(hr));
return S_OK;
}
HRESULT OggTrackAudio::PopulateSamplesVorbis2XiphLacing(const samples_t& ss)
{
packets_t::size_type packet_count = m_packets.size();
if (packet_count == 0)
return E_FAIL;
if (packet_count > 256)
return E_FAIL;
if (ss.size() != 1)
return E_FAIL;
if (m_granule_pos < 0)
return E_FAIL;
if (m_reftime < 0)
return E_FAIL;
const LONGLONG granule_pos = m_packets.back().granule_pos;
if (granule_pos < 0)
return E_FAIL;
if (granule_pos <= m_granule_pos)
return E_FAIL;
IMediaSample* const pSample = ss.front();
assert(pSample);
BYTE* tgt_ptr;
HRESULT hr = pSample->GetPointer(&tgt_ptr);
assert(SUCCEEDED(hr));
assert(tgt_ptr);
const long tgt_size = pSample->GetSize();
if (tgt_size <= 0)
return E_FAIL;
BYTE* const tgt_buf = tgt_ptr;
BYTE* const tgt_end = tgt_buf + tgt_size;
*tgt_ptr++ = static_cast<BYTE>(--packet_count); //biased count
#if 0
while (packet_count > 0)
{
assert(m_packets.size() > 1);
const OggStream::Packet& pkt = m_packets.front();
const long pkt_len = pkt.GetLength();
if (pkt_len <= 0)
return E_FAIL;
long len = pkt_len;
while (len >= 255)
{
if (tgt_ptr >= tgt_end)
return E_FAIL;
*tgt_ptr++ = 255;
len -= 255;
}
{
if (tgt_ptr >= tgt_end)
return E_FAIL;
*tgt_ptr++ = static_cast<BYTE>(len);
}
if ((tgt_ptr + pkt_len) >= tgt_end)
return E_FAIL;
const long result = pkt.Copy(m_pStream->m_pReader, tgt_ptr);
result;
assert(result == pkt_len);
if (*tgt_ptr & 0x01) //first byte of audio frame always even
return E_FAIL;
tgt_ptr += pkt_len;
assert(tgt_ptr < tgt_end);
m_packets.pop_front();
assert(!m_packets.empty());
--packet_count;
}
{
assert(m_packets.size() == 1);
const OggStream::Packet& pkt = m_packets.front();
const long pkt_len = pkt.GetLength();
if (pkt_len <= 0)
return E_FAIL;
if ((tgt_ptr + pkt_len) > tgt_end)
return E_FAIL;
const long result = pkt.Copy(m_pStream->m_pReader, tgt_ptr);
result;
assert(result == pkt_len);
if (*tgt_ptr & 0x01) //first byte for audio frame always even
return E_FAIL;
tgt_ptr += pkt_len;
assert(tgt_ptr <= tgt_end);
m_packets.pop_front();
assert(m_packets.empty());
}
#else
typedef packets_t::iterator iter_t;
iter_t iter = m_packets.begin();
const iter_t iter_end = m_packets.end();
while (packet_count > 0)
{
assert(iter != iter_end);
const OggStream::Packet& pkt = *iter++;
const long pkt_len = pkt.GetLength();
if (pkt_len <= 0)
return E_FAIL;
long len = pkt_len;
while (len >= 255)
{
if (tgt_ptr >= tgt_end)
return E_FAIL;
*tgt_ptr++ = 255;
len -= 255;
}
{
if (tgt_ptr >= tgt_end)
return E_FAIL;
*tgt_ptr++ = static_cast<BYTE>(len);
}
--packet_count;
}
assert(iter != iter_end);
++iter;
assert(iter == iter_end);
while (!m_packets.empty())
{
const OggStream::Packet& pkt = m_packets.front();
const long pkt_len = pkt.GetLength();
if (pkt_len <= 0)
return E_FAIL;
if ((tgt_ptr + pkt_len) > tgt_end)
return E_FAIL;
const long result = pkt.Copy(m_pStream->m_pReader, tgt_ptr);
result;
assert(result == pkt_len);
if (*tgt_ptr & 0x01) //first byte for audio frame always even
return E_FAIL;
tgt_ptr += pkt_len;
assert(tgt_ptr <= tgt_end);
m_packets.pop_front();
}
#endif
const long tgt_len = tgt_ptr - tgt_buf;
assert(tgt_len <= tgt_size);
hr = pSample->SetActualDataLength(tgt_len);
assert(SUCCEEDED(hr));
LONGLONG curr_samples = m_granule_pos;
LONGLONG curr_reftime = m_reftime;
m_granule_pos = granule_pos; //next_samples
const float next_samples_ = static_cast<float>(m_granule_pos);
const float samples_per_sec = static_cast<float>(m_fmt.sample_rate);
const float next_sec = next_samples_ / samples_per_sec;
const float next_reftime_ = next_sec * 10000000.0F;
m_reftime = static_cast<LONGLONG>(next_reftime_); //next_reftime
hr = pSample->SetMediaTime(&curr_samples, &m_granule_pos);
assert(SUCCEEDED(hr));
hr = pSample->SetTime(&curr_reftime, &m_reftime);
assert(SUCCEEDED(hr));
hr = pSample->SetDiscontinuity(m_bDiscontinuity ? TRUE : FALSE);
assert(SUCCEEDED(hr));
m_bDiscontinuity = false;
hr = pSample->SetSyncPoint(TRUE);
assert(SUCCEEDED(hr));
hr = pSample->SetPreroll(FALSE);
assert(SUCCEEDED(hr));
return S_OK;
}
//REFERENCE_TIME OggTrackAudio::GetCurrTime() const
//{
// assert(m_granule_pos >= 0);
// assert(m_fmt.sample_rate > 0);
//
// const double samples = static_cast<double>(m_granule_pos);
// const double samples_per_sec = m_fmt.sample_rate;
//
// const double sec = samples / samples_per_sec;
//
// const double reftime_ = sec * 10000000.0;
// const REFERENCE_TIME reftime = static_cast<REFERENCE_TIME>(reftime_);
//
// return reftime;
//}
} //end namespace WebmOggSource