| #include "oggparser.hpp" | |
| #include <cstring> | |
| #include <cassert> | |
| //#include <malloc.h> | |
| typedef std::list<oggparser::OggPage> pages_t; | |
| long oggparser::ReadInt( | |
| IOggReader* pReader, | |
| long long pos, | |
| long len, | |
| long long& val) | |
| { | |
| val = 0; | |
| for (long i = 0; i < len; ++i) | |
| { | |
| unsigned char b; | |
| const long result = pReader->Read(pos++, 1, &b); | |
| if (result < 0) //error | |
| return len; | |
| const long long bb = static_cast<long long>(b) << (i * 8); | |
| val |= bb; | |
| } | |
| return 0; //success | |
| } | |
| namespace oggparser | |
| { | |
| IOggReader::~IOggReader() | |
| { | |
| } | |
| long OggPage::Read(IOggReader* pReader, long long& pos) | |
| { | |
| if (pos < 0) | |
| return -1; | |
| long result = pReader->Read(pos, 4, capture_pattern); | |
| if (result < 0) //error | |
| return result; | |
| if (memcmp(capture_pattern, "OggS", 4) != 0) | |
| return E_FILE_FORMAT_INVALID; | |
| pos += 4; //consume capture_pattern | |
| long long version_; | |
| result = ReadInt(pReader, pos, 1, version_); | |
| if (result < 0) //error | |
| return result; | |
| ++pos; //consume version | |
| version = static_cast<unsigned char>(version_); | |
| long long header_; | |
| result = ReadInt(pReader, pos, 1, header_); | |
| if (result < 0) //error | |
| return result; | |
| ++pos; //consume header flag | |
| header = static_cast<unsigned char>(header_); | |
| result = ReadInt(pReader, pos, 8, granule_pos); | |
| if (result < 0) //error | |
| return result; | |
| pos += 8; //consume granule pos | |
| long long serial_num_; | |
| result = ReadInt(pReader, pos, 4, serial_num_); | |
| if (result < 0) //error | |
| return result; | |
| pos += 4; //consume serial number | |
| serial_num = static_cast<unsigned long>(serial_num_); | |
| long long sequence_num_; | |
| result = ReadInt(pReader, pos, 4, sequence_num_); | |
| if (result < 0) //error | |
| return result; | |
| pos += 4; //consume page sequence number | |
| sequence_num = static_cast<unsigned long>(sequence_num_); | |
| long long crc_; | |
| result = ReadInt(pReader, pos, 4, crc_); | |
| if (result < 0) //error | |
| return result; | |
| pos += 4; //consume crc | |
| //http://www.ross.net/crc/download/crc_v3.txt | |
| crc = static_cast<unsigned long>(crc_); | |
| long long segments_count; | |
| result = ReadInt(pReader, pos, 1, segments_count); | |
| if (result < 0) //error | |
| return result; | |
| if (segments_count <= 0) //TODO: confirm this | |
| return E_FILE_FORMAT_INVALID; | |
| ++pos; //consume segment count | |
| descriptors.clear(); | |
| while (segments_count > 0) | |
| { | |
| descriptors.push_back(Descriptor()); | |
| { | |
| Descriptor& data = descriptors.back(); | |
| data.pos = -1; //fill in later | |
| data.len = 0; | |
| } | |
| for (;;) | |
| { | |
| long long lacing_value; | |
| result = ReadInt(pReader, pos, 1, lacing_value); | |
| if (result < 0) //error | |
| return result; | |
| ++pos; //consume lacing value | |
| { | |
| Descriptor& payload = descriptors.back(); | |
| payload.len += static_cast<long>(lacing_value); | |
| if (--segments_count <= 0) | |
| { | |
| if (lacing_value == 255) //pkt continued on next page | |
| payload.len = -payload.len; | |
| else //pkt completed on curr page | |
| header |= OggPage::fDone; | |
| break; | |
| } | |
| } | |
| if (lacing_value != 255) | |
| break; | |
| } | |
| } | |
| typedef descriptors_t::iterator iter_t; | |
| iter_t iter = descriptors.begin(); | |
| const iter_t iter_end = descriptors.end(); | |
| while (iter != iter_end) | |
| { | |
| Descriptor& payload = *iter++; | |
| payload.pos = pos; | |
| pos += labs(payload.len); | |
| } | |
| return 0; | |
| } | |
| #if 0 | |
| long OggStream::Create(IOggReader* pReader, OggStream*& pStream) | |
| { | |
| if (pReader == NULL) | |
| return -1; | |
| //the stream of bytes we have is the "physical" bitstream. | |
| //there are the bytes stored in the container file. | |
| //the physical bitstream encapsulates one or more "logical" | |
| //bitstreams, provided by the encoder. i assume this is | |
| //the same as a matroska track or an avi stream. | |
| //a logical bitream contains "packets", which is the same | |
| //thing as a frame. | |
| //a physical bitstream comprises "pages". does a single page | |
| //contain only single logical bitstream or can it include | |
| //multiple logical bistreams??? [one logical bitstream only] | |
| //a page has a unique serial number that identifies the | |
| //logical bitstream. this is analogous to a matroska track number | |
| //or an avi stream identifier | |
| //a logical bitstream has a special(?) start page (bos) and | |
| //another special(?) stop page (eos) | |
| //the bos identifies the codec type, for audio it contains | |
| //sampling rate, etc | |
| //bos uses a magic number to uniquely identify the codec type | |
| //the format of a bos is specific to each codec type | |
| //ogg allows "secondary header packets" to follow bos but | |
| //precede data packets; these are put on an integral number | |
| //of pages, which do not also have data packets | |
| //"Ogg Vorbis" is a "media mapping" that describes how the | |
| //logical bitstream for vorbis audio is encapsulated into | |
| //an ogg physical bitstream. | |
| //bos | |
| // name of vorbis codec | |
| // revision of vorbis codec | |
| // audio rate | |
| // audio quality | |
| //two more header pages | |
| // | |
| //bos: 1st 7 bytes are: | |
| // 0x01 | |
| // "vorbis" | |
| //data for a logical bitstream comes in order and has | |
| //position markers called "Granule positions". This is an | |
| //alternative to using "time" directly: position markers are | |
| //unitless and increase sequentially. To get a time you | |
| //must convert the granule position. | |
| //Ogg pages have a max size of 64kB (65307); this means that a large | |
| //packet ("frame") will need to be distributed over one or more | |
| //pages. | |
| //a packet is itself divided into 255-byte chunks called "segments", | |
| //plus one more segment with fewer than 255 bytes | |
| //the segments (which have no header boundaries themselves) are | |
| //grouped into a page preceded by a header. The page header | |
| //contains a "segment table" gives info about the sizes ("lacing") | |
| //of the segments. | |
| //a flag in page hdr tells whether this page contains a packet | |
| //continued from a previous page. | |
| //from rfc5334.txt: | |
| //"In particular, .ogg is used for Ogg files that | |
| //contain only a Vorbis bitstream," | |
| //Vorbis_I_spec.html | |
| // | |
| //A.2 Encapsulation | |
| //Ogg encapsulation of a Vorbis packet stream is straightforward. | |
| //The first Vorbis packet (the identification header), | |
| //which uniquely identifies a stream as Vorbis audio, | |
| //is placed alone in the first page of the logical Ogg stream. | |
| //This results in a first Ogg page of exactly 58 bytes at the | |
| //very beginning of the logical stream. | |
| //This first page is marked Â’beginning of streamÂ’ in the page flags. | |
| //The second and third vorbis packets (comment and setup headers) | |
| //may span one or more pages beginning on the second page of the | |
| //logical stream. However many pages they span, the third header | |
| //packet finishes the page on which it ends. The next (first audio) | |
| //packet must begin on a fresh page. | |
| //The granule position of these first pages containing only | |
| //headers is zero. | |
| //The first audio packet of the logical stream begins a fresh Ogg page. | |
| //Packets are placed into ogg pages in order until the end of stream. | |
| //The last page is marked Â’end of streamÂ’ in the page flags. | |
| //Vorbis packets may span page boundaries. | |
| //The granule position of pages containing Vorbis audio is in units of | |
| //PCM audio samples (per channel; a stereo streamÂ’s granule position does | |
| //not increment at twice the speed of a mono stream). | |
| //The granule position of a page represents the end PCM sample position | |
| //of the last packet completed on that page. The Â’last PCM sampleÂ’ is | |
| //the last complete sample returned by decode, not an internal sample | |
| //awaiting lapping with a subsequent block. A page that is entirely | |
| //spanned by a single packet (that completes on a subsequent page) | |
| //has no granule position, and the granule position is set to Â’-1Â’. | |
| // | |
| //Note that the last decoded (fully lapped) PCM sample from a packet | |
| //is not necessarily the middle sample from that block. If, eg, | |
| //the current Vorbis packet encodes a ”long block” and the next | |
| //Vorbis packet encodes a ”short block”, the last decodable sample | |
| //from the current packet be at position | |
| // (3*long_block_length/4) - (short_block_length/4). | |
| //The granule (PCM) position of the first page need not indicate that | |
| //the stream started at position zero. Although the granule position | |
| //belongs to the last completed packet on the page and a valid | |
| //granule position must be positive, by inference it may indicate | |
| //that the PCM position of the beginning of audio is positive or negative. | |
| // | |
| //A positive starting value simply indicates that this stream begins | |
| //at some positive time offset, potentially within a larger program. | |
| //This is a common case when connecting to the middle of broadcast stream. | |
| // | |
| //A negative value indicates that output samples preceeding time zero | |
| //should be discarded during decoding; this technique is used to allow | |
| //sample-granularity editing of the stream start time of already-encoded | |
| //Vorbis streams. The number of samples to be discarded must not exceed | |
| //the overlap-add span of the first two audio packets. | |
| // | |
| //In both of these cases in which the initial audio PCM starting | |
| //offset is nonzero, the second finished audio packet must flush | |
| //the page on which it appears and the third packet begin a fresh page. | |
| //This allows the decoder to always be able to perform PCM position | |
| //adjustments before needing to return any PCM data from synthesis, | |
| //resulting in correct positioning information without any aditional | |
| //seeking logic. | |
| // | |
| //Note: Failure to do so should, at worst, cause a decoder implementation | |
| //to return incorrect positioning information for seeking operations at | |
| //the very beginning of the stream. | |
| //A granule position on the final page in a stream that indicates | |
| //less audio data than the final packet would normally return is | |
| //used to end the stream on other than even frame boundaries. | |
| //The difference between the actual available data returned and | |
| //the declared amount indicates how many trailing samples to | |
| //discard from the decoding process. | |
| //rfc3533.txt: | |
| // | |
| //6. The Ogg page format | |
| // A physical Ogg bitstream consists of a sequence of concatenated | |
| // pages. Pages are of variable size, usually 4-8 kB, maximum 65307 | |
| // bytes. A page header contains all the information needed to | |
| // demultiplex the logical bitstreams out of the physical bitstream and | |
| // to perform basic error recovery and landmarks for seeking. Each page | |
| // is a self-contained entity such that the page decode mechanism can | |
| // recognize, verify, and handle single pages at a time without | |
| // requiring the overall bitstream. | |
| // The Ogg page header has the following format: | |
| // 0 1 2 3 | |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1| Byte | |
| //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| //| capture_pattern: Magic number for page start "OggS" | 0-3 | |
| //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| //| version | header_type | granule_position | 4-7 | |
| //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| //| | 8-11 | |
| //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| //| | bitstream_serial_number | 12-15 | |
| //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| //| | page_sequence_number | 16-19 | |
| //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| //| | CRC_checksum | 20-23 | |
| //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| //| |page_segments | segment_table | 24-27 | |
| //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| //| ... | 28- | |
| //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
| // The LSb (least significant bit) comes first in the Bytes. Fields | |
| // with more than one byte length are encoded LSB (least significant | |
| // byte) first. | |
| pStream = new OggStream(pReader); | |
| //Vorbis_I_spec.html | |
| //Section A.2 | |
| //Ogg page | |
| //http://en.wikipedia.org/wiki/Ogg_page | |
| const long result = pStream->Init(); | |
| if (result >= 0) | |
| return 0; //must capture ident, comment, and setup pkts | |
| delete pStream; | |
| pStream = NULL; | |
| return result; | |
| } | |
| #endif | |
| OggStream::OggStream(IOggReader* p) : | |
| m_pReader(p), | |
| m_page_num(0), | |
| m_page_base(0), | |
| m_pos(0), | |
| m_base(0), | |
| m_serial_num(0) | |
| { | |
| } | |
| OggStream::~OggStream() | |
| { | |
| } | |
| long OggStream::Init( | |
| Packet& ident, | |
| Packet& comment, | |
| Packet& setup) | |
| { | |
| assert(m_packets.empty()); | |
| assert(m_page_num == 0); | |
| OggPage page; | |
| for (;;) | |
| { | |
| long result = page.Read(m_pReader, m_pos); | |
| if (result < 0) | |
| return result; | |
| if (!(page.header & OggPage::fBOS)) | |
| return E_FILE_FORMAT_INVALID; | |
| if (OggPage::Match(page.descriptors, m_pReader, "\x01vorbis") >= 1) | |
| break; | |
| } | |
| if (page.descriptors.size() != 1) | |
| return E_FILE_FORMAT_INVALID; | |
| if (OggPage::GetLength(page.descriptors) != 30) | |
| return E_FILE_FORMAT_INVALID; | |
| if (page.header & OggPage::fContinued) | |
| return E_FILE_FORMAT_INVALID; | |
| //if (!(page.header & OggPage::fBOS)) | |
| // return E_FILE_FORMAT_INVALID; | |
| if (page.header & OggPage::fEOS) | |
| return E_FILE_FORMAT_INVALID; | |
| if (!(page.header & OggPage::fDone)) | |
| return E_FILE_FORMAT_INVALID; | |
| if (page.granule_pos != 0) | |
| return E_FILE_FORMAT_INVALID; | |
| if (page.sequence_num != m_page_num++) | |
| return E_FILE_FORMAT_INVALID; | |
| m_serial_num = page.serial_num; | |
| ident.descriptors = page.descriptors; | |
| ident.granule_pos = 0; | |
| long result = GetPacket(comment, 10); | |
| if (result < 0) | |
| return result; | |
| if (comment.IsHeader(m_pReader, "\x03vorbis") <= 0) | |
| return E_FILE_FORMAT_INVALID; | |
| result = GetPacket(setup, 10); | |
| if (result < 0) | |
| return result; | |
| if (setup.IsHeader(m_pReader, "\x05vorbis") <= 0) | |
| return E_FILE_FORMAT_INVALID; | |
| if (!m_packets.empty()) | |
| return E_FILE_FORMAT_INVALID; | |
| m_base = m_pos; | |
| m_page_base = m_page_num; | |
| return 0; | |
| } | |
| long OggStream::Reset() | |
| { | |
| m_pos = m_base; | |
| m_page_num = m_page_base; | |
| m_packets.clear(); | |
| return 0; //success | |
| } | |
| #if 0 | |
| long OggStream::GetPackets(OggPage& page, long long page_pos) | |
| { | |
| #if 0 | |
| const long long page_pos = m_pos; | |
| long result = page.Read(m_pReader, m_pos); | |
| if (result < 0) //error | |
| return result; | |
| #endif | |
| assert(!page.descriptors.empty()); | |
| if (page.header & OggPage::fContinued) | |
| { | |
| if (m_packets.empty()) | |
| return E_FILE_FORMAT_INVALID; | |
| Packet& pkt = m_packets.back(); | |
| OggPage::descriptors_t& dd = pkt.descriptors; | |
| if (!dd.empty()) //predicate should always be true | |
| { | |
| OggPage::Descriptor& d = dd.back(); | |
| if (d.len >= 0) | |
| return E_FILE_FORMAT_INVALID; | |
| d.len = labs(d.len); | |
| } | |
| dd.push_back(page.descriptors.front()); | |
| page.descriptors.pop_front(); | |
| } | |
| else if (!m_packets.empty()) | |
| { | |
| const Packet& pkt = m_packets.back(); | |
| const OggPage::descriptors_t& dd = pkt.descriptors; | |
| if (!dd.empty()) //predicate should always be true | |
| { | |
| const OggPage::Descriptor& d = dd.back(); | |
| if (d.len < 0) | |
| return E_FILE_FORMAT_INVALID; | |
| } | |
| } | |
| OggPage::descriptors_t& dd = page.descriptors; | |
| while (!dd.empty()) | |
| { | |
| OggPage::Descriptor& d = dd.front(); | |
| m_packets.push_back(Packet()); | |
| Packet& pkt = m_packets.back(); | |
| pkt.descriptors.push_back(d); | |
| pkt.granule_pos = -1; | |
| dd.pop_front(); | |
| } | |
| assert(!m_packets.empty()); | |
| if (page.granule_pos < 0) //no packet was completed by this page | |
| { | |
| const Packet& pkt = m_packets.back(); | |
| const OggPage::descriptors_t& dd = pkt.descriptors; | |
| if (!dd.empty()) //predicate should always be true | |
| { | |
| const OggPage::Descriptor& d = dd.back(); | |
| if (d.len >= 0) //not incomplete | |
| return E_FILE_FORMAT_INVALID; | |
| } | |
| return 0; //no granule pos, so nothing else to do just yet | |
| } | |
| typedef packets_t::reverse_iterator iter_t; | |
| iter_t iter = m_packets.rbegin(); | |
| const iter_t iter_end = m_packets.rend(); | |
| while (iter != iter_end) | |
| { | |
| Packet& pkt = *iter++; | |
| OggPage::descriptors_t& dd = pkt.descriptors; | |
| if (dd.empty()) //weird | |
| continue; | |
| OggPage::Descriptor& d = dd.back(); | |
| if (d.len < 0) //last packet wasn't completed on this cluster | |
| continue; //try earlier packet | |
| if (d.pos <= page_pos) //we have navigated off of curr page | |
| return E_FILE_FORMAT_INVALID; | |
| assert(pkt.granule_pos < 0); | |
| pkt.granule_pos = page.granule_pos; | |
| return 0; | |
| } | |
| return E_FILE_FORMAT_INVALID; | |
| //Granule pos info: | |
| //http://lists.xiph.org/pipermail/vorbis/2005-September/025955.html | |
| // | |
| //> If this is true: | |
| //> | |
| //> "Granule Position Information in Ogg Header is a hint | |
| //> for the decoder and gives some timing and position | |
| //> information." | |
| //> | |
| //> So say if granule position is 10000, it means that | |
| //> 10000 PCM samples are encoded in this page | |
| //> approximately. | |
| // | |
| //Incorrect. It means that if you decode up to the end of this page from | |
| //the beginning of the stream, you'll have a total of precisely 10000 | |
| //samples (assuming all the data was there, and you didn't lose any). | |
| //Thus it gives precise and absolute positioning information, as | |
| //required for seeking, for instance. | |
| //> | |
| //> If this is true we can neglect this information, it | |
| //> will not effect the decoding right(but might effect | |
| //> for streaming)? | |
| // | |
| //Incorrect. Vorbis sort of 'overloads' this field; it must be provided | |
| //to the decoder for correct handling of beginning and end of stream. | |
| // | |
| //You also need it for seeking, obviously. | |
| } | |
| #endif | |
| long OggStream::ParsePage() | |
| { | |
| if (m_pos < 0) | |
| return E_END_OF_FILE; | |
| OggPage page; | |
| const long long page_pos = m_pos; | |
| const long result = page.Read(m_pReader, m_pos); | |
| if (result < 0) //error | |
| return result; | |
| if (page.serial_num != m_serial_num) | |
| return 0; | |
| if (page.sequence_num != m_page_num++) | |
| return E_FILE_FORMAT_INVALID; | |
| assert(!page.descriptors.empty()); | |
| if (page.header & OggPage::fContinued) | |
| { | |
| if (m_packets.empty()) | |
| return E_FILE_FORMAT_INVALID; | |
| Packet& pkt = m_packets.back(); | |
| OggPage::descriptors_t& dd = pkt.descriptors; | |
| if (!dd.empty()) //predicate should always be true | |
| { | |
| OggPage::Descriptor& d = dd.back(); | |
| if (d.len >= 0) | |
| return E_FILE_FORMAT_INVALID; | |
| d.len = labs(d.len); | |
| } | |
| dd.push_back(page.descriptors.front()); | |
| page.descriptors.pop_front(); | |
| } | |
| else if (!m_packets.empty()) | |
| { | |
| const Packet& pkt = m_packets.back(); | |
| const OggPage::descriptors_t& dd = pkt.descriptors; | |
| if (!dd.empty()) //predicate should always be true | |
| { | |
| const OggPage::Descriptor& d = dd.back(); | |
| if (d.len < 0) | |
| return E_FILE_FORMAT_INVALID; | |
| } | |
| } | |
| OggPage::descriptors_t& dd = page.descriptors; | |
| while (!dd.empty()) | |
| { | |
| OggPage::Descriptor& d = dd.front(); | |
| m_packets.push_back(Packet()); | |
| Packet& pkt = m_packets.back(); | |
| pkt.descriptors.push_back(d); | |
| pkt.granule_pos = -1; | |
| dd.pop_front(); | |
| } | |
| assert(!m_packets.empty()); | |
| if (page.granule_pos < 0) //no packet was completed by this page | |
| { | |
| const Packet& pkt = m_packets.back(); | |
| const OggPage::descriptors_t& dd = pkt.descriptors; | |
| if (!dd.empty()) //predicate should always be true | |
| { | |
| const OggPage::Descriptor& d = dd.back(); | |
| if (d.len >= 0) //not incomplete | |
| return E_FILE_FORMAT_INVALID; | |
| } | |
| return 0; //no granule pos, so nothing else to do just yet | |
| } | |
| typedef packets_t::reverse_iterator iter_t; | |
| iter_t iter = m_packets.rbegin(); | |
| const iter_t iter_end = m_packets.rend(); | |
| while (iter != iter_end) | |
| { | |
| Packet& pkt = *iter++; | |
| OggPage::descriptors_t& dd = pkt.descriptors; | |
| if (dd.empty()) //weird | |
| continue; | |
| OggPage::Descriptor& d = dd.back(); | |
| if (d.len < 0) //last packet wasn't completed on this cluster | |
| continue; //try earlier packet | |
| if (d.pos <= page_pos) //we have navigated off of curr page | |
| return E_FILE_FORMAT_INVALID; | |
| assert(pkt.granule_pos < 0); | |
| pkt.granule_pos = page.granule_pos; | |
| if (page.header & OggPage::fEOS) | |
| m_pos = -1; //means end-of-stream | |
| return 0; | |
| } | |
| return E_FILE_FORMAT_INVALID; | |
| } | |
| long OggStream::GetPacket(Packet& pkt) | |
| { | |
| return GetPacket(pkt, 0); | |
| } | |
| long OggStream::GetPacket(Packet& pkt, int max_count) | |
| { | |
| int count = 0; | |
| while (ParsePacket(pkt) < 1) | |
| { | |
| const long result = ParsePage(); | |
| if (result < 0) //error | |
| return result; | |
| ++count; | |
| if ((max_count > 0) && (count >= max_count)) | |
| return E_FILE_FORMAT_INVALID; | |
| } | |
| return 1; //success | |
| } | |
| long OggStream::ParsePacket(Packet& pkt_) | |
| { | |
| if (m_packets.empty()) | |
| return 0; //no packet available for consumption | |
| const Packet& pkt = m_packets.front(); | |
| const OggPage::descriptors_t& dd = pkt.descriptors; | |
| assert(!dd.empty()); | |
| const OggPage::Descriptor& d = dd.back(); | |
| if (d.len < 0) //hasn't been completed yet | |
| return 0; //packet not available for consumption yet | |
| pkt_ = pkt; | |
| m_packets.pop_front(); | |
| return 1; //successfully consumed pkt | |
| } | |
| long OggPage::GetLength(const descriptors_t& dd) | |
| { | |
| long result = 0; | |
| typedef descriptors_t::const_iterator iter_t; | |
| iter_t i = dd.begin(); | |
| const iter_t j = dd.end(); | |
| while (i != j) | |
| { | |
| const Descriptor& d = *i++; | |
| if (d.len < 0) | |
| return -1; | |
| result += d.len; | |
| } | |
| return result; | |
| } | |
| long OggStream::Packet::GetLength() const | |
| { | |
| return OggPage::GetLength(descriptors); | |
| } | |
| long OggPage::Copy( | |
| const descriptors_t& dd, | |
| IOggReader* pReader, | |
| unsigned char* buf) | |
| { | |
| if (buf == NULL) | |
| return -1; | |
| unsigned char* const buf_base = buf; | |
| typedef descriptors_t::const_iterator iter_t; | |
| iter_t i = dd.begin(); | |
| const iter_t j = dd.end(); | |
| while (i != j) | |
| { | |
| const Descriptor& d = *i++; | |
| const long result = pReader->Read(d.pos, d.len, buf); | |
| if (result < 0) //error | |
| return result; | |
| buf += d.len; | |
| } | |
| const ptrdiff_t len_ = buf - buf_base; | |
| const long len = static_cast<long>(len_); | |
| return len; | |
| } | |
| long OggStream::Packet::Copy( | |
| IOggReader* pReader, | |
| unsigned char* buf) const | |
| { | |
| return OggPage::Copy(descriptors, pReader, buf); | |
| } | |
| long OggStream::Packet::IsHeader(IOggReader* pReader, const char* str) const | |
| { | |
| return OggPage::Match(descriptors, pReader, str); | |
| } | |
| long OggPage::Match( | |
| const descriptors_t& dd, | |
| IOggReader* pReader, | |
| const char* str) | |
| { | |
| if (pReader == NULL) | |
| return -1; | |
| if (str == NULL) | |
| return -1; | |
| typedef OggPage::descriptors_t::const_iterator iter_t; | |
| iter_t i = dd.begin(); | |
| const iter_t j = dd.end(); | |
| while (i != j) | |
| { | |
| const OggPage::Descriptor& d = *i++; | |
| long long pos = d.pos; | |
| const long long pos_end = d.pos + d.len; | |
| while ((*str != '\0') && (pos != pos_end)) | |
| { | |
| unsigned char c; | |
| const long result = pReader->Read(pos++, 1, &c); | |
| if (result < 0) //error | |
| return result; | |
| if (*str++ != c) | |
| return 0; //does not match | |
| } | |
| if (*str == '\0') | |
| return 1; //match | |
| } | |
| return -1; //ran out of payload to compare | |
| } | |
| long VorbisIdent::Read(IOggReader* pReader, const OggStream::Packet& ident) | |
| { | |
| if (pReader == NULL) | |
| return -1; | |
| const OggPage::descriptors_t& dd = ident.descriptors; | |
| if (dd.size() != 1) | |
| return -1; | |
| const OggPage::Descriptor& d = dd.front(); | |
| //58 = size of first page | |
| //26 + 1 + 1 = 28 | |
| //30 = size of ident pkt | |
| if (d.len != 30) //weird | |
| return -1; | |
| long long pos = d.pos; | |
| unsigned char h[7]; | |
| long result = pReader->Read(pos, 7, h); | |
| if (result < 0) | |
| return result; | |
| pos += 7; | |
| if (memcmp(h, "\x01vorbis", 7) != 0) | |
| return -1; | |
| long long val; | |
| result = ReadInt(pReader, pos, 4, val); | |
| if (result < 0) | |
| return result; | |
| pos += 4; //consume version | |
| version = static_cast<unsigned long>(val); | |
| result = ReadInt(pReader, pos, 1, val); | |
| if (result < 0) | |
| return result; | |
| ++pos; //consume channels | |
| channels = static_cast<unsigned char>(val); | |
| result = ReadInt(pReader, pos, 4, val); | |
| if (result < 0) | |
| return result; | |
| pos += 4; //consume sample_rate | |
| sample_rate = static_cast<unsigned long>(val); | |
| result = ReadInt(pReader, pos, 4, val); | |
| if (result < 0) | |
| return result; | |
| pos += 4; //consume bitrate_max | |
| bitrate_maximum = static_cast<long>(val); | |
| result = ReadInt(pReader, pos, 4, val); | |
| if (result < 0) | |
| return result; | |
| pos += 4; //consume bitrate_avg | |
| bitrate_nominal = static_cast<long>(val); | |
| result = ReadInt(pReader, pos, 4, val); | |
| if (result < 0) | |
| return result; | |
| pos += 4; //consume bitrate_min | |
| bitrate_minimum = static_cast<long>(val); | |
| unsigned char b; | |
| result = pReader->Read(pos, 1, &b); | |
| if (result < 0) | |
| return result; | |
| ++pos; | |
| int e = b & 0x0F; | |
| blocksize_0 = 1 << e; | |
| e = (b & 0xF0) >> 4; | |
| blocksize_1 = 1 << e; | |
| result = pReader->Read(pos, 1, &b); | |
| if (result < 0) | |
| return result; | |
| ++pos; | |
| assert((pos - d.pos) == 30); | |
| framing = (b & 0x01); | |
| return 0; | |
| } | |
| } //end namespace oggparser |