/********** 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 .) 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 **********/ // Copyright (c) 1996-2020, Live Networks, Inc. All rights reserved // A test program that reads a ".ogg" (i.e., Ogg) file, demultiplexes each track // (audio and/or video), and streams each track using RTP multicast. // main program #include #include #include UsageEnvironment* env; char const* inputFileName = "test.ogg"; struct in_addr destinationAddress; RTSPServer* rtspServer; ServerMediaSession* sms; OggFile* oggFile; OggDemux* oggDemux; unsigned numTracks; // A structure representing the state of a track: struct TrackState { u_int32_t trackNumber; FramedSource* source; RTPSink* sink; RTCPInstance* rtcp; }; TrackState* trackState; void onOggFileCreation(OggFile* newFile, void* clientData); // forward int main(int argc, char** argv) { // Begin by setting up our usage environment: TaskScheduler* scheduler = BasicTaskScheduler::createNew(); env = BasicUsageEnvironment::createNew(*scheduler); // Define our destination (multicast) IP address: destinationAddress.s_addr = chooseRandomIPv4SSMAddress(*env); // Note: This is a multicast address. If you wish instead to stream // using unicast, then you should use the "testOnDemandRTSPServer" // test program - not this test program - as a model. // Create our RTSP server. (Receivers will need to use RTSP to access the stream.) rtspServer = RTSPServer::createNew(*env, 8554); if (rtspServer == NULL) { *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n"; exit(1); } sms = ServerMediaSession::createNew(*env, "testStream", inputFileName, "Session streamed by \"testMKVStreamer\"", True /*SSM*/); // Arrange to create an "OggFile" object for the specified file. // (Note that this object is not created immediately, but instead via a callback.) OggFile::createNew(*env, inputFileName, onOggFileCreation, NULL); env->taskScheduler().doEventLoop(); // does not return return 0; // only to prevent compiler warning } void play(); // forward void onOggFileCreation(OggFile* newFile, void* clientData) { oggFile = newFile; // Create a new demultiplexor for the file: oggDemux = oggFile->newDemux(); // Create source streams, "RTPSink"s, and "RTCPInstance"s for each preferred track; unsigned short rtpPortNum = 22222; const unsigned char ttl = 255; const unsigned maxCNAMElen = 100; unsigned char CNAME[maxCNAMElen+1]; gethostname((char*)CNAME, maxCNAMElen); CNAME[maxCNAMElen] = '\0'; // just in case numTracks = oggFile->numTracks(); trackState = new TrackState[numTracks]; for (unsigned i = 0; i < numTracks; ++i) { u_int32_t trackNumber; FramedSource* baseSource = oggDemux->newDemuxedTrack(trackNumber); trackState[i].trackNumber = trackNumber; unsigned estBitrate, numFiltersInFrontOfTrack; trackState[i].source = oggFile ->createSourceForStreaming(baseSource, trackNumber, estBitrate, numFiltersInFrontOfTrack); trackState[i].sink = NULL; // by default; may get changed below trackState[i].rtcp = NULL; // ditto if (trackState[i].source != NULL) { Groupsock* rtpGroupsock = new Groupsock(*env, destinationAddress, rtpPortNum, ttl); Groupsock* rtcpGroupsock = new Groupsock(*env, destinationAddress, rtpPortNum+1, ttl); rtpPortNum += 2; trackState[i].sink = oggFile->createRTPSinkForTrackNumber(trackNumber, rtpGroupsock, 96+i); if (trackState[i].sink != NULL) { if (trackState[i].sink->estimatedBitrate() > 0) { estBitrate = trackState[i].sink->estimatedBitrate(); // hack } trackState[i].rtcp = RTCPInstance::createNew(*env, rtcpGroupsock, estBitrate, CNAME, trackState[i].sink, NULL /* we're a server */, True /* we're a SSM source */); // Note: This starts RTCP running automatically // Having set up a track for streaming, add it to our RTSP server's "ServerMediaSession": sms->addSubsession(PassiveServerMediaSubsession::createNew(*trackState[i].sink, trackState[i].rtcp)); } } } if (sms->numSubsessions() == 0) { *env << "Error: The Ogg file \"" << inputFileName << "\" has no streamable tracks\n"; *env << "(Perhaps the file does not exist, is not an 'Ogg' file, or has no tracks that we know how to stream.)\n"; exit(1); } rtspServer->addServerMediaSession(sms); char* url = rtspServer->rtspURL(sms); *env << "Play this stream using the URL \"" << url << "\"\n"; delete[] url; // Start the streaming: play(); } void afterPlaying(void* /*clientData*/) { *env << "...done reading from file\n"; // Stop playing all "RTPSink"s, then close the source streams // (which will also close the demultiplexor itself): unsigned i; for (i = 0; i < numTracks; ++i) { if (trackState[i].sink != NULL) trackState[i].sink->stopPlaying(); Medium::close(trackState[i].source); trackState[i].source = NULL; } // Create a new demultiplexor from our Ogg file, then new data sources for each track: oggDemux = oggFile->newDemux(); for (i = 0; i < numTracks; ++i) { if (trackState[i].trackNumber != 0) { FramedSource* baseSource = oggDemux->newDemuxedTrack(trackState[i].trackNumber); unsigned estBitrate, numFiltersInFrontOfTrack; trackState[i].source = oggFile->createSourceForStreaming(baseSource, trackState[i].trackNumber, estBitrate, numFiltersInFrontOfTrack); } } // Start playing once again: play(); } void play() { *env << "Beginning to read from file...\n"; // Start playing each track's RTP sink from its corresponding source: for (unsigned i = 0; i < numTracks; ++i) { if (trackState[i].sink != NULL && trackState[i].source != NULL) { trackState[i].sink->startPlaying(*trackState[i].source, afterPlaying, NULL); } } }