| 1 | /********** |
| 2 | This library is free software; you can redistribute it and/or modify it under |
| 3 | the terms of the GNU Lesser General Public License as published by the |
| 4 | Free Software Foundation; either version 3 of the License, or (at your |
| 5 | option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.) |
| 6 | |
| 7 | This library is distributed in the hope that it will be useful, but WITHOUT |
| 8 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 9 | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for |
| 10 | more details. |
| 11 | |
| 12 | You should have received a copy of the GNU Lesser General Public License |
| 13 | along with this library; if not, write to the Free Software Foundation, Inc., |
| 14 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 15 | **********/ |
| 16 | // "liveMedia" |
| 17 | // Copyright (c) 1996-2020 Live Networks, Inc. All rights reserved. |
| 18 | // A class that encapsulates an Ogg file. |
| 19 | // Implementation |
| 20 | |
| 21 | #include "OggFileParser.hh" |
| 22 | #include "OggDemuxedTrack.hh" |
| 23 | #include "ByteStreamFileSource.hh" |
| 24 | #include "VorbisAudioRTPSink.hh" |
| 25 | #include "SimpleRTPSink.hh" |
| 26 | #include "TheoraVideoRTPSink.hh" |
| 27 | |
| 28 | ////////// OggTrackTable definition ///////// |
| 29 | |
| 30 | // For looking up and iterating over the file's tracks: |
| 31 | |
| 32 | class OggTrackTable { |
| 33 | public: |
| 34 | OggTrackTable(); |
| 35 | virtual ~OggTrackTable(); |
| 36 | |
| 37 | void add(OggTrack* newTrack); |
| 38 | OggTrack* lookup(u_int32_t trackNumber); |
| 39 | |
| 40 | unsigned numTracks() const; |
| 41 | |
| 42 | private: |
| 43 | friend class OggTrackTableIterator; |
| 44 | HashTable* fTable; |
| 45 | }; |
| 46 | |
| 47 | |
| 48 | ////////// OggFile implementation ////////// |
| 49 | |
| 50 | void OggFile::createNew(UsageEnvironment& env, char const* fileName, |
| 51 | onCreationFunc* onCreation, void* onCreationClientData) { |
| 52 | new OggFile(env, fileName, onCreation, onCreationClientData); |
| 53 | } |
| 54 | |
| 55 | OggTrack* OggFile::lookup(u_int32_t trackNumber) { |
| 56 | return fTrackTable->lookup(trackNumber); |
| 57 | } |
| 58 | |
| 59 | OggDemux* OggFile::newDemux() { |
| 60 | OggDemux* demux = new OggDemux(*this); |
| 61 | fDemuxesTable->Add((char const*)demux, demux); |
| 62 | |
| 63 | return demux; |
| 64 | } |
| 65 | |
| 66 | unsigned OggFile::numTracks() const { |
| 67 | return fTrackTable->numTracks(); |
| 68 | } |
| 69 | |
| 70 | FramedSource* OggFile |
| 71 | ::createSourceForStreaming(FramedSource* baseSource, u_int32_t trackNumber, |
| 72 | unsigned& estBitrate, unsigned& numFiltersInFrontOfTrack) { |
| 73 | if (baseSource == NULL) return NULL; |
| 74 | |
| 75 | FramedSource* result = baseSource; // by default |
| 76 | numFiltersInFrontOfTrack = 0; // by default |
| 77 | |
| 78 | // Look at the track's MIME type to set its estimated bitrate (for use by RTCP). |
| 79 | // (Later, try to be smarter about figuring out the bitrate.) ##### |
| 80 | // Some MIME types also require adding a special 'framer' in front of the source. |
| 81 | OggTrack* track = lookup(trackNumber); |
| 82 | if (track != NULL) { // should always be true |
| 83 | estBitrate = track->estBitrate; |
| 84 | } |
| 85 | |
| 86 | return result; |
| 87 | } |
| 88 | |
| 89 | RTPSink* OggFile |
| 90 | ::createRTPSinkForTrackNumber(u_int32_t trackNumber, Groupsock* rtpGroupsock, |
| 91 | unsigned char rtpPayloadTypeIfDynamic) { |
| 92 | OggTrack* track = lookup(trackNumber); |
| 93 | if (track == NULL || track->mimeType == NULL) return NULL; |
| 94 | |
| 95 | RTPSink* result = NULL; // default value for unknown media types |
| 96 | |
| 97 | if (strcmp(track->mimeType, "audio/VORBIS" ) == 0) { |
| 98 | // For Vorbis audio, we use the special "identification", "comment", and "setup" headers |
| 99 | // that we read when we initially read the headers at the start of the file: |
| 100 | result = VorbisAudioRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, |
| 101 | track->samplingFrequency, track->numChannels, |
| 102 | track->vtoHdrs.header[0], track->vtoHdrs.headerSize[0], |
| 103 | track->vtoHdrs.header[1], track->vtoHdrs.headerSize[1], |
| 104 | track->vtoHdrs.header[2], track->vtoHdrs.headerSize[2]); |
| 105 | } else if (strcmp(track->mimeType, "audio/OPUS" ) == 0) { |
| 106 | result = SimpleRTPSink |
| 107 | ::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, |
| 108 | 48000, "audio" , "OPUS" , 2, False/*only 1 Opus 'packet' in each RTP packet*/); |
| 109 | } else if (strcmp(track->mimeType, "video/THEORA" ) == 0) { |
| 110 | // For Theora video, we use the special "identification", "comment", and "setup" headers |
| 111 | // that we read when we initially read the headers at the start of the file: |
| 112 | result = TheoraVideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, |
| 113 | track->vtoHdrs.header[0], track->vtoHdrs.headerSize[0], |
| 114 | track->vtoHdrs.header[1], track->vtoHdrs.headerSize[1], |
| 115 | track->vtoHdrs.header[2], track->vtoHdrs.headerSize[2]); |
| 116 | } |
| 117 | |
| 118 | return result; |
| 119 | } |
| 120 | |
| 121 | |
| 122 | OggFile::OggFile(UsageEnvironment& env, char const* fileName, |
| 123 | onCreationFunc* onCreation, void* onCreationClientData) |
| 124 | : Medium(env), |
| 125 | fFileName(strDup(fileName)), |
| 126 | fOnCreation(onCreation), fOnCreationClientData(onCreationClientData) { |
| 127 | fTrackTable = new OggTrackTable; |
| 128 | fDemuxesTable = HashTable::create(ONE_WORD_HASH_KEYS); |
| 129 | |
| 130 | FramedSource* inputSource = ByteStreamFileSource::createNew(envir(), fileName); |
| 131 | if (inputSource == NULL) { |
| 132 | // The specified input file does not exist! |
| 133 | fParserForInitialization = NULL; |
| 134 | handleEndOfBosPageParsing(); // we have no file, and thus no tracks, but we still need to signal this |
| 135 | } else { |
| 136 | // Initialize ourselves by parsing the file's headers: |
| 137 | fParserForInitialization |
| 138 | = new OggFileParser(*this, inputSource, handleEndOfBosPageParsing, this); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | OggFile::~OggFile() { |
| 143 | delete fParserForInitialization; |
| 144 | |
| 145 | // Delete any outstanding "OggDemux"s, and the table for them: |
| 146 | OggDemux* demux; |
| 147 | while ((demux = (OggDemux*)fDemuxesTable->RemoveNext()) != NULL) { |
| 148 | delete demux; |
| 149 | } |
| 150 | delete fDemuxesTable; |
| 151 | delete fTrackTable; |
| 152 | |
| 153 | delete[] (char*)fFileName; |
| 154 | } |
| 155 | |
| 156 | void OggFile::handleEndOfBosPageParsing(void* clientData) { |
| 157 | ((OggFile*)clientData)->handleEndOfBosPageParsing(); |
| 158 | } |
| 159 | |
| 160 | void OggFile::handleEndOfBosPageParsing() { |
| 161 | // Delete our parser, because it's done its job now: |
| 162 | delete fParserForInitialization; fParserForInitialization = NULL; |
| 163 | |
| 164 | // Finally, signal our caller that we've been created and initialized: |
| 165 | if (fOnCreation != NULL) (*fOnCreation)(this, fOnCreationClientData); |
| 166 | } |
| 167 | |
| 168 | void OggFile::addTrack(OggTrack* newTrack) { |
| 169 | fTrackTable->add(newTrack); |
| 170 | } |
| 171 | |
| 172 | void OggFile::removeDemux(OggDemux* demux) { |
| 173 | fDemuxesTable->Remove((char const*)demux); |
| 174 | } |
| 175 | |
| 176 | |
| 177 | ////////// OggTrackTable implementation ///////// |
| 178 | |
| 179 | OggTrackTable::OggTrackTable() |
| 180 | : fTable(HashTable::create(ONE_WORD_HASH_KEYS)) { |
| 181 | } |
| 182 | |
| 183 | OggTrackTable::~OggTrackTable() { |
| 184 | // Remove and delete all of our "OggTrack" descriptors, and the hash table itself: |
| 185 | OggTrack* track; |
| 186 | while ((track = (OggTrack*)fTable->RemoveNext()) != NULL) { |
| 187 | delete track; |
| 188 | } |
| 189 | delete fTable; |
| 190 | } |
| 191 | |
| 192 | void OggTrackTable::add(OggTrack* newTrack) { |
| 193 | OggTrack* existingTrack |
| 194 | = (OggTrack*)fTable->Add((char const*)newTrack->trackNumber, newTrack); |
| 195 | delete existingTrack; // if any |
| 196 | } |
| 197 | |
| 198 | OggTrack* OggTrackTable::lookup(u_int32_t trackNumber) { |
| 199 | return (OggTrack*)fTable->Lookup((char const*)trackNumber); |
| 200 | } |
| 201 | |
| 202 | unsigned OggTrackTable::numTracks() const { return fTable->numEntries(); } |
| 203 | |
| 204 | OggTrackTableIterator::OggTrackTableIterator(OggTrackTable& ourTable) { |
| 205 | fIter = HashTable::Iterator::create(*(ourTable.fTable)); |
| 206 | } |
| 207 | |
| 208 | OggTrackTableIterator::~OggTrackTableIterator() { |
| 209 | delete fIter; |
| 210 | } |
| 211 | |
| 212 | OggTrack* OggTrackTableIterator::next() { |
| 213 | char const* key; |
| 214 | return (OggTrack*)fIter->next(key); |
| 215 | } |
| 216 | |
| 217 | |
| 218 | ////////// OggTrack implementation ////////// |
| 219 | |
| 220 | OggTrack::OggTrack() |
| 221 | : trackNumber(0), mimeType(NULL), |
| 222 | samplingFrequency(48000), numChannels(2), estBitrate(100) { // default settings |
| 223 | vtoHdrs.header[0] = vtoHdrs.header[1] = vtoHdrs.header[2] = NULL; |
| 224 | vtoHdrs.headerSize[0] = vtoHdrs.headerSize[1] = vtoHdrs.headerSize[2] = 0; |
| 225 | |
| 226 | vtoHdrs.vorbis_mode_count = 0; |
| 227 | vtoHdrs.vorbis_mode_blockflag = NULL; |
| 228 | } |
| 229 | |
| 230 | OggTrack::~OggTrack() { |
| 231 | delete[] vtoHdrs.header[0]; delete[] vtoHdrs.header[1]; delete[] vtoHdrs.header[2]; |
| 232 | delete[] vtoHdrs.vorbis_mode_blockflag; |
| 233 | } |
| 234 | |
| 235 | |
| 236 | ///////// OggDemux implementation ///////// |
| 237 | |
| 238 | FramedSource* OggDemux::newDemuxedTrack(u_int32_t& resultTrackNumber) { |
| 239 | OggTrack* nextTrack; |
| 240 | do { |
| 241 | nextTrack = fIter->next(); |
| 242 | } while (nextTrack != NULL && nextTrack->mimeType == NULL); |
| 243 | |
| 244 | if (nextTrack == NULL) { // no more tracks |
| 245 | resultTrackNumber = 0; |
| 246 | return NULL; |
| 247 | } |
| 248 | |
| 249 | resultTrackNumber = nextTrack->trackNumber; |
| 250 | FramedSource* trackSource = new OggDemuxedTrack(envir(), resultTrackNumber, *this); |
| 251 | fDemuxedTracksTable->Add((char const*)resultTrackNumber, trackSource); |
| 252 | return trackSource; |
| 253 | } |
| 254 | |
| 255 | FramedSource* OggDemux::newDemuxedTrackByTrackNumber(unsigned trackNumber) { |
| 256 | if (trackNumber == 0) return NULL; |
| 257 | |
| 258 | FramedSource* trackSource = new OggDemuxedTrack(envir(), trackNumber, *this); |
| 259 | fDemuxedTracksTable->Add((char const*)trackNumber, trackSource); |
| 260 | return trackSource; |
| 261 | } |
| 262 | |
| 263 | OggDemuxedTrack* OggDemux::lookupDemuxedTrack(u_int32_t trackNumber) { |
| 264 | return (OggDemuxedTrack*)fDemuxedTracksTable->Lookup((char const*)trackNumber); |
| 265 | } |
| 266 | |
| 267 | OggDemux::OggDemux(OggFile& ourFile) |
| 268 | : Medium(ourFile.envir()), |
| 269 | fOurFile(ourFile), fDemuxedTracksTable(HashTable::create(ONE_WORD_HASH_KEYS)), |
| 270 | fIter(new OggTrackTableIterator(*fOurFile.fTrackTable)) { |
| 271 | FramedSource* fileSource = ByteStreamFileSource::createNew(envir(), ourFile.fileName()); |
| 272 | fOurParser = new OggFileParser(ourFile, fileSource, handleEndOfFile, this, this); |
| 273 | } |
| 274 | |
| 275 | OggDemux::~OggDemux() { |
| 276 | // Begin by acting as if we've reached the end of the source file. |
| 277 | // This should cause all of our demuxed tracks to get closed. |
| 278 | handleEndOfFile(); |
| 279 | |
| 280 | // Then delete our table of "OggDemuxedTrack"s |
| 281 | // - but not the "OggDemuxedTrack"s themselves; that should have already happened: |
| 282 | delete fDemuxedTracksTable; |
| 283 | |
| 284 | delete fIter; |
| 285 | delete fOurParser; |
| 286 | fOurFile.removeDemux(this); |
| 287 | } |
| 288 | |
| 289 | void OggDemux::removeTrack(u_int32_t trackNumber) { |
| 290 | fDemuxedTracksTable->Remove((char const*)trackNumber); |
| 291 | if (fDemuxedTracksTable->numEntries() == 0) { |
| 292 | // We no longer have any demuxed tracks, so delete ourselves now: |
| 293 | delete this; |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | void OggDemux::continueReading() { |
| 298 | fOurParser->continueParsing(); |
| 299 | } |
| 300 | |
| 301 | void OggDemux::handleEndOfFile(void* clientData) { |
| 302 | ((OggDemux*)clientData)->handleEndOfFile(); |
| 303 | } |
| 304 | |
| 305 | void OggDemux::handleEndOfFile() { |
| 306 | // Iterate through all of our 'demuxed tracks', handling 'end of input' on each one. |
| 307 | // Hack: Because this can cause the hash table to get modified underneath us, |
| 308 | // we don't call the handlers until after we've first iterated through all of the tracks. |
| 309 | unsigned numTracks = fDemuxedTracksTable->numEntries(); |
| 310 | if (numTracks == 0) return; |
| 311 | OggDemuxedTrack** tracks = new OggDemuxedTrack*[numTracks]; |
| 312 | |
| 313 | HashTable::Iterator* iter = HashTable::Iterator::create(*fDemuxedTracksTable); |
| 314 | unsigned i; |
| 315 | char const* trackNumber; |
| 316 | |
| 317 | for (i = 0; i < numTracks; ++i) { |
| 318 | tracks[i] = (OggDemuxedTrack*)iter->next(trackNumber); |
| 319 | } |
| 320 | delete iter; |
| 321 | |
| 322 | for (i = 0; i < numTracks; ++i) { |
| 323 | if (tracks[i] == NULL) continue; // sanity check; shouldn't happen |
| 324 | tracks[i]->handleClosure(); |
| 325 | } |
| 326 | |
| 327 | delete[] tracks; |
| 328 | } |
| 329 | |