/********** 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 streams a WAV audio file via RTP/RTCP // main program #include "liveMedia.hh" #include "GroupsockHelper.hh" #include "BasicUsageEnvironment.hh" // To convert 16-bit samples to 8-bit u-law ("u" is the Greek letter "mu") // encoding, before streaming, uncomment the following line: //#define CONVERT_TO_ULAW 1 UsageEnvironment* env; void play(); // forward int main(int argc, char** argv) { // Begin by setting up our usage environment: TaskScheduler* scheduler = BasicTaskScheduler::createNew(); env = BasicUsageEnvironment::createNew(*scheduler); play(); env->taskScheduler().doEventLoop(); // does not return return 0; // only to prevent compiler warnings } char const* inputFileName = "test.wav"; void afterPlaying(void* clientData); // forward // A structure to hold the state of the current session. // It is used in the "afterPlaying()" function to clean up the session. struct sessionState_t { FramedSource* source; RTPSink* sink; RTCPInstance* rtcpInstance; Groupsock* rtpGroupsock; Groupsock* rtcpGroupsock; RTSPServer* rtspServer; } sessionState; void play() { // Open the file as a 'WAV' file: WAVAudioFileSource* wavSource = WAVAudioFileSource::createNew(*env, inputFileName); if (wavSource == NULL) { *env << "Unable to open file \"" << inputFileName << "\" as a WAV audio file source: " << env->getResultMsg() << "\n"; exit(1); } // Get attributes of the audio source: unsigned char audioFormat = wavSource->getAudioFormat(); unsigned char const bitsPerSample = wavSource->bitsPerSample(); // We handle only 4,8,16,20,24 bits-per-sample audio: if (bitsPerSample%4 != 0 || bitsPerSample < 4 || bitsPerSample > 24 || bitsPerSample == 12) { *env << "The input file contains " << bitsPerSample << " bit-per-sample audio, which we don't handle\n"; exit(1); } unsigned const samplingFrequency = wavSource->samplingFrequency(); unsigned char const numChannels = wavSource->numChannels(); unsigned bitsPerSecond = samplingFrequency*bitsPerSample*numChannels; *env << "Audio source parameters:\n\t" << samplingFrequency << " Hz, "; *env << bitsPerSample << " bits-per-sample, "; *env << numChannels << " channels => "; *env << bitsPerSecond << " bits-per-second\n"; char const* mimeType; unsigned char payloadFormatCode = 96; // by default, unless a static RTP payload type can be used // Add in any filter necessary to transform the data prior to streaming. // (This is where any audio compression would get added.) sessionState.source = wavSource; // by default if (audioFormat == WA_PCM) { if (bitsPerSample == 16) { // Note that samples in the WAV audio file are in little-endian order. #ifdef CONVERT_TO_ULAW // Add a filter that converts from raw 16-bit PCM audio (in little-endian order) to 8-bit u-law audio: sessionState.source = uLawFromPCMAudioSource::createNew(*env, wavSource, 1/*little-endian*/); if (sessionState.source == NULL) { *env << "Unable to create a u-law filter from the PCM audio source: " << env->getResultMsg() << "\n"; exit(1); } bitsPerSecond /= 2; *env << "Converting to 8-bit u-law audio for streaming => " << bitsPerSecond << " bits-per-second\n"; mimeType = "PCMU"; if (samplingFrequency == 8000 && numChannels == 1) { payloadFormatCode = 0; // a static RTP payload type } #else // Add a filter that converts from little-endian to network (big-endian) order: sessionState.source = EndianSwap16::createNew(*env, wavSource); if (sessionState.source == NULL) { *env << "Unable to create a little->bit-endian order filter from the PCM audio source: " << env->getResultMsg() << "\n"; exit(1); } *env << "Converting to network byte order for streaming\n"; mimeType = "L16"; if (samplingFrequency == 44100 && numChannels == 2) { payloadFormatCode = 10; // a static RTP payload type } else if (samplingFrequency == 44100 && numChannels == 1) { payloadFormatCode = 11; // a static RTP payload type } #endif } else if (bitsPerSample == 20 || bitsPerSample == 24) { // Add a filter that converts from little-endian to network (big-endian) order: sessionState.source = EndianSwap24::createNew(*env, wavSource); if (sessionState.source == NULL) { *env << "Unable to create a little->bit-endian order filter from the PCM audio source: " << env->getResultMsg() << "\n"; exit(1); } *env << "Converting to network byte order for streaming\n"; mimeType = bitsPerSample == 20 ? "L20" : "L24"; } else { // bitsPerSample == 8 (we assume that bitsPerSample == 4 is only for WA_IMA_ADPCM) // Don't do any transformation; send the 8-bit PCM data 'as is': mimeType = "L8"; } } else if (audioFormat == WA_PCMU) { mimeType = "PCMU"; if (samplingFrequency == 8000 && numChannels == 1) { payloadFormatCode = 0; // a static RTP payload type } } else if (audioFormat == WA_PCMA) { mimeType = "PCMA"; if (samplingFrequency == 8000 && numChannels == 1) { payloadFormatCode = 8; // a static RTP payload type } } else if (audioFormat == WA_IMA_ADPCM) { mimeType = "DVI4"; // Use a static payload type, if one is defined: if (numChannels == 1) { if (samplingFrequency == 8000) { payloadFormatCode = 5; // a static RTP payload type } else if (samplingFrequency == 16000) { payloadFormatCode = 6; // a static RTP payload type } else if (samplingFrequency == 11025) { payloadFormatCode = 16; // a static RTP payload type } else if (samplingFrequency == 22050) { payloadFormatCode = 17; // a static RTP payload type } } } else { //unknown format *env << "Unknown audio format code \"" << audioFormat << "\" in WAV file header\n"; exit(1); } // Create 'groupsocks' for RTP and RTCP: struct in_addr destinationAddress; 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" demo application, // or the "LIVE555 Media Server" - not this application - as a model. const unsigned short rtpPortNum = 2222; const unsigned short rtcpPortNum = rtpPortNum+1; const unsigned char ttl = 255; const Port rtpPort(rtpPortNum); const Port rtcpPort(rtcpPortNum); sessionState.rtpGroupsock = new Groupsock(*env, destinationAddress, rtpPort, ttl); sessionState.rtpGroupsock->multicastSendOnly(); // we're a SSM source sessionState.rtcpGroupsock = new Groupsock(*env, destinationAddress, rtcpPort, ttl); sessionState.rtcpGroupsock->multicastSendOnly(); // we're a SSM source // Create an appropriate audio RTP sink (using "SimpleRTPSink") from the RTP 'groupsock': sessionState.sink = SimpleRTPSink::createNew(*env, sessionState.rtpGroupsock, payloadFormatCode, samplingFrequency, "audio", mimeType, numChannels); // Create (and start) a 'RTCP instance' for this RTP sink: const unsigned estimatedSessionBandwidth = (bitsPerSecond + 500)/1000; // in kbps; for RTCP b/w share const unsigned maxCNAMElen = 100; unsigned char CNAME[maxCNAMElen+1]; gethostname((char*)CNAME, maxCNAMElen); CNAME[maxCNAMElen] = '\0'; // just in case sessionState.rtcpInstance = RTCPInstance::createNew(*env, sessionState.rtcpGroupsock, estimatedSessionBandwidth, CNAME, sessionState.sink, NULL /* we're a server */, True /* we're a SSM source*/); // Note: This starts RTCP running automatically // Create and start a RTSP server to serve this stream: sessionState.rtspServer = RTSPServer::createNew(*env, 8554); if (sessionState.rtspServer == NULL) { *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n"; exit(1); } ServerMediaSession* sms = ServerMediaSession::createNew(*env, "testStream", inputFileName, "Session streamed by \"testWAVAudiotreamer\"", True/*SSM*/); sms->addSubsession(PassiveServerMediaSubsession::createNew(*sessionState.sink, sessionState.rtcpInstance)); sessionState.rtspServer->addServerMediaSession(sms); char* url = sessionState.rtspServer->rtspURL(sms); *env << "Play this stream using the URL \"" << url << "\"\n"; delete[] url; // Finally, start the streaming: *env << "Beginning streaming...\n"; sessionState.sink->startPlaying(*sessionState.source, afterPlaying, NULL); } void afterPlaying(void* /*clientData*/) { *env << "...done streaming\n"; // End by closing the media: Medium::close(sessionState.rtspServer); Medium::close(sessionState.rtcpInstance); Medium::close(sessionState.sink); delete sessionState.rtpGroupsock; Medium::close(sessionState.source); delete sessionState.rtcpGroupsock; // We're done: exit(0); }