| /********** |
| 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 outputs each track to a file. |
| // main program |
| |
| #include <liveMedia.hh> |
| #include <BasicUsageEnvironment.hh> |
| |
| UsageEnvironment* env; |
| char const* programName; |
| char const* inputFileName; |
| |
| // An array of structures representing the state of the video, audio, and subtitle tracks: |
| static struct { |
| unsigned trackNumber; |
| FramedSource* source; |
| FileSink* sink; |
| } trackState[3]; |
| |
| void onMatroskaFileCreation(MatroskaFile* newFile, void* clientData); // forward |
| |
| void usage() { |
| *env << "usage: " << programName << " <input-Matroska-or-WebM-file-name>\n"; |
| exit(1); |
| } |
| |
| int main(int argc, char** argv) { |
| // Begin by setting up our usage environment: |
| TaskScheduler* scheduler = BasicTaskScheduler::createNew(); |
| env = BasicUsageEnvironment::createNew(*scheduler); |
| |
| // Parse the command line: |
| programName = argv[0]; |
| if (argc != 2) usage(); |
| inputFileName = argv[1]; |
| |
| // 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* matroskaFile, void* /*clientData*/) { |
| // Create a new demultiplexor for the file: |
| MatroskaDemux* matroskaDemux = matroskaFile->newDemux(); |
| |
| // Create source streams and file sinks for each preferred track; |
| |
| unsigned numActiveTracks = 0; |
| for (unsigned i = 0; i < 3; ++i) { |
| unsigned trackNumber; |
| trackState[i].source = matroskaDemux->newDemuxedTrack(trackNumber); |
| trackState[i].trackNumber = trackNumber; |
| trackState[i].sink = NULL; // by default; may get changed below |
| |
| if (trackState[i].source == NULL) continue; |
| |
| char const* mimeType = matroskaFile->trackMIMEType(trackNumber); |
| if (mimeType == NULL || mimeType[0] == '\0') continue; |
| fprintf(stderr, "#####@@@@@ MatroskaDemuxedTrack for mimeType %s is %p\n", mimeType, trackState[i].source); |
| |
| // Create the file name from "mimeType" by replacing "/" with "-", and adding the |
| // track number at the end: |
| char* fileName = new char[strlen(mimeType) + 100/*more than enough space*/]; |
| sprintf(fileName, "%s-%d", mimeType, trackNumber); |
| for (unsigned j = 0; fileName[j] != '\0'; ++j) { |
| if (fileName[j] == '/') { |
| fileName[j] = '-'; |
| break; |
| } |
| } |
| |
| trackState[i].sink |
| = matroskaFile->createFileSinkForTrackNumber(trackNumber, fileName); |
| if (trackState[i].sink != NULL) { |
| ++numActiveTracks; |
| fprintf(stderr, "Created output file \"%s\" for track %d\n", fileName, trackNumber); |
| } |
| } |
| |
| if (numActiveTracks == 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); |
| } |
| |
| // Start the streaming: |
| play(); |
| } |
| |
| void afterPlaying(void* /*clientData*/) { |
| *env << "...done reading from file\n"; |
| |
| // Stop playing all sinks, 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; |
| } |
| |
| // Finally, close the sinks: |
| for (i = 0; i < 3; ++i) Medium::close(trackState[i].sink); |
| |
| exit(0); |
| } |
| |
| 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); |
| } |
| } |
| } |