| /********** |
| 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 3 of the License, or (at your |
| option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.) |
| |
| 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-2020 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; |
| } |