| /********** |
| 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 |
| **********/ |
| // Copyright (c) 1996-2020, Live Networks, Inc. All rights reserved |
| // A test program that reads a ".mkv" (i.e., Matroska) file, demultiplexes each track |
| // (video, audio, subtitles), and streams each track using RTP multicast. |
| // main program |
| |
| #include <liveMedia.hh> |
| #include <BasicUsageEnvironment.hh> |
| #include <GroupsockHelper.hh> |
| |
| UsageEnvironment* env; |
| char const* inputFileName = "test.mkv"; |
| struct in_addr destinationAddress; |
| RTSPServer* rtspServer; |
| ServerMediaSession* sms; |
| MatroskaFile* matroskaFile; |
| MatroskaDemux* matroskaDemux; |
| |
| // An array of structures representing the state of the video, audio, and subtitle tracks: |
| static struct { |
| unsigned trackNumber; |
| FramedSource* source; |
| RTPSink* sink; |
| RTCPInstance* rtcp; |
| } trackState[3]; |
| |
| void onMatroskaFileCreation(MatroskaFile* 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 a "MatroskaFile" object for the specified file. |
| // (Note that this object is not created immediately, but instead via a callback.) |
| MatroskaFile::createNew(*env, inputFileName, onMatroskaFileCreation, NULL); |
| |
| env->taskScheduler().doEventLoop(); // does not return |
| |
| return 0; // only to prevent compiler warning |
| } |
| |
| void play(); // forward |
| |
| void onMatroskaFileCreation(MatroskaFile* newFile, void* /*clientData*/) { |
| matroskaFile = newFile; |
| |
| // Create a new demultiplexor for the file: |
| matroskaDemux = matroskaFile->newDemux(); |
| |
| // Create source streams, "RTPSink"s, and "RTCPInstance"s for each preferred track; |
| unsigned short rtpPortNum = 44444; |
| const unsigned char ttl = 255; |
| |
| const unsigned maxCNAMElen = 100; |
| unsigned char CNAME[maxCNAMElen+1]; |
| gethostname((char*)CNAME, maxCNAMElen); |
| CNAME[maxCNAMElen] = '\0'; // just in case |
| |
| for (unsigned i = 0; i < 3; ++i) { |
| unsigned trackNumber; |
| FramedSource* baseSource = matroskaDemux->newDemuxedTrack(trackNumber); |
| trackState[i].trackNumber = trackNumber; |
| |
| unsigned estBitrate, numFiltersInFrontOfTrack; |
| trackState[i].source = matroskaFile |
| ->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 |
| = matroskaFile->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 Matroska file \"" << inputFileName << "\" has no streamable tracks\n"; |
| *env << "(Perhaps the file does not exist, or is not a 'Matroska' file.)\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 < 3; ++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 Matroska file, then new data sources for each track: |
| matroskaDemux = matroskaFile->newDemux(); |
| for (i = 0; i < 3; ++i) { |
| if (trackState[i].trackNumber != 0) { |
| FramedSource* baseSource |
| = matroskaDemux->newDemuxedTrackByTrackNumber(trackState[i].trackNumber); |
| |
| unsigned estBitrate, numFiltersInFrontOfTrack; |
| trackState[i].source = matroskaFile |
| ->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 < 3; ++i) { |
| if (trackState[i].sink != NULL && trackState[i].source != NULL) { |
| trackState[i].sink->startPlaying(*trackState[i].source, afterPlaying, NULL); |
| } |
| } |
| } |