329 lines
11 KiB
C++
329 lines
11 KiB
C++
|
/**********
|
||
|
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 <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-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;
|
||
|
}
|