| /********** |
| 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 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); |
| } |