/********** This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. (See .) This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA **********/ // "liveMedia" // Copyright (c) 1996-2016 Live Networks, Inc. All rights reserved. // A class that encapsulates an Ogg file. // Implementation #include "OggFileParser.hh" #include "OggDemuxedTrack.hh" #include "ByteStreamFileSource.hh" #include "VorbisAudioRTPSink.hh" #include "SimpleRTPSink.hh" #include "TheoraVideoRTPSink.hh" ////////// OggTrackTable definition ///////// // For looking up and iterating over the file's tracks: class OggTrackTable { public: OggTrackTable(); virtual ~OggTrackTable(); void add(OggTrack* newTrack); OggTrack* lookup(u_int32_t trackNumber); unsigned numTracks() const; private: friend class OggTrackTableIterator; HashTable* fTable; }; ////////// OggFile implementation ////////// void OggFile::createNew(UsageEnvironment& env, char const* fileName, onCreationFunc* onCreation, void* onCreationClientData) { new OggFile(env, fileName, onCreation, onCreationClientData); } OggTrack* OggFile::lookup(u_int32_t trackNumber) { return fTrackTable->lookup(trackNumber); } OggDemux* OggFile::newDemux() { OggDemux* demux = new OggDemux(*this); fDemuxesTable->Add((char const*)demux, demux); return demux; } unsigned OggFile::numTracks() const { return fTrackTable->numTracks(); } FramedSource* OggFile ::createSourceForStreaming(FramedSource* baseSource, u_int32_t trackNumber, unsigned& estBitrate, unsigned& numFiltersInFrontOfTrack) { if (baseSource == NULL) return NULL; FramedSource* result = baseSource; // by default numFiltersInFrontOfTrack = 0; // by default // Look at the track's MIME type to set its estimated bitrate (for use by RTCP). // (Later, try to be smarter about figuring out the bitrate.) ##### // Some MIME types also require adding a special 'framer' in front of the source. OggTrack* track = lookup(trackNumber); if (track != NULL) { // should always be true estBitrate = track->estBitrate; } return result; } RTPSink* OggFile ::createRTPSinkForTrackNumber(u_int32_t trackNumber, Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic) { OggTrack* track = lookup(trackNumber); if (track == NULL || track->mimeType == NULL) return NULL; RTPSink* result = NULL; // default value for unknown media types if (strcmp(track->mimeType, "audio/VORBIS") == 0) { // For Vorbis audio, we use the special "identification", "comment", and "setup" headers // that we read when we initially read the headers at the start of the file: result = VorbisAudioRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, track->samplingFrequency, track->numChannels, track->vtoHdrs.header[0], track->vtoHdrs.headerSize[0], track->vtoHdrs.header[1], track->vtoHdrs.headerSize[1], track->vtoHdrs.header[2], track->vtoHdrs.headerSize[2]); } else if (strcmp(track->mimeType, "audio/OPUS") == 0) { result = SimpleRTPSink ::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, 48000, "audio", "OPUS", 2, False/*only 1 Opus 'packet' in each RTP packet*/); } else if (strcmp(track->mimeType, "video/THEORA") == 0) { // For Theora video, we use the special "identification", "comment", and "setup" headers // that we read when we initially read the headers at the start of the file: result = TheoraVideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, track->vtoHdrs.header[0], track->vtoHdrs.headerSize[0], track->vtoHdrs.header[1], track->vtoHdrs.headerSize[1], track->vtoHdrs.header[2], track->vtoHdrs.headerSize[2]); } return result; } OggFile::OggFile(UsageEnvironment& env, char const* fileName, onCreationFunc* onCreation, void* onCreationClientData) : Medium(env), fFileName(strDup(fileName)), fOnCreation(onCreation), fOnCreationClientData(onCreationClientData) { fTrackTable = new OggTrackTable; fDemuxesTable = HashTable::create(ONE_WORD_HASH_KEYS); FramedSource* inputSource = ByteStreamFileSource::createNew(envir(), fileName); if (inputSource == NULL) { // The specified input file does not exist! fParserForInitialization = NULL; handleEndOfBosPageParsing(); // we have no file, and thus no tracks, but we still need to signal this } else { // Initialize ourselves by parsing the file's headers: fParserForInitialization = new OggFileParser(*this, inputSource, handleEndOfBosPageParsing, this); } } OggFile::~OggFile() { delete fParserForInitialization; // Delete any outstanding "OggDemux"s, and the table for them: OggDemux* demux; while ((demux = (OggDemux*)fDemuxesTable->RemoveNext()) != NULL) { delete demux; } delete fDemuxesTable; delete fTrackTable; delete[] (char*)fFileName; } void OggFile::handleEndOfBosPageParsing(void* clientData) { ((OggFile*)clientData)->handleEndOfBosPageParsing(); } void OggFile::handleEndOfBosPageParsing() { // Delete our parser, because it's done its job now: delete fParserForInitialization; fParserForInitialization = NULL; // Finally, signal our caller that we've been created and initialized: if (fOnCreation != NULL) (*fOnCreation)(this, fOnCreationClientData); } void OggFile::addTrack(OggTrack* newTrack) { fTrackTable->add(newTrack); } void OggFile::removeDemux(OggDemux* demux) { fDemuxesTable->Remove((char const*)demux); } ////////// OggTrackTable implementation ///////// OggTrackTable::OggTrackTable() : fTable(HashTable::create(ONE_WORD_HASH_KEYS)) { } OggTrackTable::~OggTrackTable() { // Remove and delete all of our "OggTrack" descriptors, and the hash table itself: OggTrack* track; while ((track = (OggTrack*)fTable->RemoveNext()) != NULL) { delete track; } delete fTable; } void OggTrackTable::add(OggTrack* newTrack) { OggTrack* existingTrack = (OggTrack*)fTable->Add((char const*)newTrack->trackNumber, newTrack); delete existingTrack; // if any } OggTrack* OggTrackTable::lookup(u_int32_t trackNumber) { return (OggTrack*)fTable->Lookup((char const*)trackNumber); } unsigned OggTrackTable::numTracks() const { return fTable->numEntries(); } OggTrackTableIterator::OggTrackTableIterator(OggTrackTable& ourTable) { fIter = HashTable::Iterator::create(*(ourTable.fTable)); } OggTrackTableIterator::~OggTrackTableIterator() { delete fIter; } OggTrack* OggTrackTableIterator::next() { char const* key; return (OggTrack*)fIter->next(key); } ////////// OggTrack implementation ////////// OggTrack::OggTrack() : trackNumber(0), mimeType(NULL), samplingFrequency(48000), numChannels(2), estBitrate(100) { // default settings vtoHdrs.header[0] = vtoHdrs.header[1] = vtoHdrs.header[2] = NULL; vtoHdrs.headerSize[0] = vtoHdrs.headerSize[1] = vtoHdrs.headerSize[2] = 0; vtoHdrs.vorbis_mode_count = 0; vtoHdrs.vorbis_mode_blockflag = NULL; } OggTrack::~OggTrack() { delete[] vtoHdrs.header[0]; delete[] vtoHdrs.header[1]; delete[] vtoHdrs.header[2]; delete[] vtoHdrs.vorbis_mode_blockflag; } ///////// OggDemux implementation ///////// FramedSource* OggDemux::newDemuxedTrack(u_int32_t& resultTrackNumber) { OggTrack* nextTrack; do { nextTrack = fIter->next(); } while (nextTrack != NULL && nextTrack->mimeType == NULL); if (nextTrack == NULL) { // no more tracks resultTrackNumber = 0; return NULL; } resultTrackNumber = nextTrack->trackNumber; FramedSource* trackSource = new OggDemuxedTrack(envir(), resultTrackNumber, *this); fDemuxedTracksTable->Add((char const*)resultTrackNumber, trackSource); return trackSource; } FramedSource* OggDemux::newDemuxedTrackByTrackNumber(unsigned trackNumber) { if (trackNumber == 0) return NULL; FramedSource* trackSource = new OggDemuxedTrack(envir(), trackNumber, *this); fDemuxedTracksTable->Add((char const*)trackNumber, trackSource); return trackSource; } OggDemuxedTrack* OggDemux::lookupDemuxedTrack(u_int32_t trackNumber) { return (OggDemuxedTrack*)fDemuxedTracksTable->Lookup((char const*)trackNumber); } OggDemux::OggDemux(OggFile& ourFile) : Medium(ourFile.envir()), fOurFile(ourFile), fDemuxedTracksTable(HashTable::create(ONE_WORD_HASH_KEYS)), fIter(new OggTrackTableIterator(*fOurFile.fTrackTable)) { FramedSource* fileSource = ByteStreamFileSource::createNew(envir(), ourFile.fileName()); fOurParser = new OggFileParser(ourFile, fileSource, handleEndOfFile, this, this); } OggDemux::~OggDemux() { // Begin by acting as if we've reached the end of the source file. // This should cause all of our demuxed tracks to get closed. handleEndOfFile(); // Then delete our table of "OggDemuxedTrack"s // - but not the "OggDemuxedTrack"s themselves; that should have already happened: delete fDemuxedTracksTable; delete fIter; delete fOurParser; fOurFile.removeDemux(this); } void OggDemux::removeTrack(u_int32_t trackNumber) { fDemuxedTracksTable->Remove((char const*)trackNumber); if (fDemuxedTracksTable->numEntries() == 0) { // We no longer have any demuxed tracks, so delete ourselves now: delete this; } } void OggDemux::continueReading() { fOurParser->continueParsing(); } void OggDemux::handleEndOfFile(void* clientData) { ((OggDemux*)clientData)->handleEndOfFile(); } void OggDemux::handleEndOfFile() { // Iterate through all of our 'demuxed tracks', handling 'end of input' on each one. // Hack: Because this can cause the hash table to get modified underneath us, // we don't call the handlers until after we've first iterated through all of the tracks. unsigned numTracks = fDemuxedTracksTable->numEntries(); if (numTracks == 0) return; OggDemuxedTrack** tracks = new OggDemuxedTrack*[numTracks]; HashTable::Iterator* iter = HashTable::Iterator::create(*fDemuxedTracksTable); unsigned i; char const* trackNumber; for (i = 0; i < numTracks; ++i) { tracks[i] = (OggDemuxedTrack*)iter->next(trackNumber); } delete iter; for (i = 0; i < numTracks; ++i) { if (tracks[i] == NULL) continue; // sanity check; shouldn't happen tracks[i]->handleClosure(); } delete[] tracks; }