RTMP custom audio loop support for streams containing copyrighted audio
This commit is contained in:
		
							parent
							
								
									86379e44eb
								
							
						
					
					
						commit
						d8f67e84b7
					
				
					 3 changed files with 324 additions and 21 deletions
				
			
		| 
						 | 
					@ -471,6 +471,7 @@ bool FLV::Tag::DTSCAudioInit(const std::string & codec, unsigned int sampleRate,
 | 
				
			||||||
  if (len <= 0 || !checkBufferSize()){return false;}
 | 
					  if (len <= 0 || !checkBufferSize()){return false;}
 | 
				
			||||||
  memcpy(data + 13, initData.c_str(), len - 17);
 | 
					  memcpy(data + 13, initData.c_str(), len - 17);
 | 
				
			||||||
  data[12] = 0; // AAC sequence header
 | 
					  data[12] = 0; // AAC sequence header
 | 
				
			||||||
 | 
					  // Contains: SoundFormat = AAC = 1010(4b), SoundRate (2b), SoundSize (1b), SoundType(1b) = 1010 0000
 | 
				
			||||||
  data[11] = 0xA0; 
 | 
					  data[11] = 0xA0; 
 | 
				
			||||||
  if (sampleRate >= 44100){
 | 
					  if (sampleRate >= 44100){
 | 
				
			||||||
    data[11] |= 0x0C;
 | 
					    data[11] |= 0x0C;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,11 @@ namespace Mist{
 | 
				
			||||||
  OutRTMP::OutRTMP(Socket::Connection &conn) : Output(conn){
 | 
					  OutRTMP::OutRTMP(Socket::Connection &conn) : Output(conn){
 | 
				
			||||||
    lastSilence = 0;
 | 
					    lastSilence = 0;
 | 
				
			||||||
    hasSilence = false;
 | 
					    hasSilence = false;
 | 
				
			||||||
 | 
					    lastAudioInserted = 0;
 | 
				
			||||||
 | 
					    hasCustomAudio = false;
 | 
				
			||||||
 | 
					    customAudioSize = 0;
 | 
				
			||||||
 | 
					    customAudioIterator = 0;
 | 
				
			||||||
 | 
					    currentFrameTimestamp = 0;
 | 
				
			||||||
    lastAck = Util::bootSecs();
 | 
					    lastAck = Util::bootSecs();
 | 
				
			||||||
    lastOutTime = 0;
 | 
					    lastOutTime = 0;
 | 
				
			||||||
    setRtmpOffset = false;
 | 
					    setRtmpOffset = false;
 | 
				
			||||||
| 
						 | 
					@ -260,7 +265,23 @@ namespace Mist{
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void OutRTMP::sendSilence(uint64_t timestamp){
 | 
					  void OutRTMP::sendSilence(uint64_t timestamp){
 | 
				
			||||||
 | 
					    /* 
 | 
				
			||||||
 | 
					    Byte 1:
 | 
				
			||||||
 | 
					      SoundFormat     = 4 bits    : AAC   = 10  = 1010
 | 
				
			||||||
 | 
					      SoundRate       = 2 bits    : 44Khz = 3   = 11
 | 
				
			||||||
 | 
					      SoundSize       = 1 bit     : always 1
 | 
				
			||||||
 | 
					      SoundType       = 1 bit     : always 1 (Stereo) for AAC
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Byte 2->:
 | 
				
			||||||
 | 
					      SoundData
 | 
				
			||||||
 | 
					      Since it is an AAC stream SoundData is an AACAUDIODATA object:
 | 
				
			||||||
 | 
					        AACPacketType = 8 bits     : always 1 (0 indicates sequence header which is sent in sendHeader)
 | 
				
			||||||
 | 
					        Data[N times] = 8 bits     : Raw AAC frame data
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    tmpData: 10101111 00000001 = af 01 = \257 \001 + raw AAC silence
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
    const char * tmpData = "\257\001!\020\004`\214\034";
 | 
					    const char * tmpData = "\257\001!\020\004`\214\034";
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    size_t data_len = 8;
 | 
					    size_t data_len = 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    char rtmpheader[] ={0,                // byte 0 = cs_id | ch_type
 | 
					    char rtmpheader[] ={0,                // byte 0 = cs_id | ch_type
 | 
				
			||||||
| 
						 | 
					@ -269,6 +290,7 @@ namespace Mist{
 | 
				
			||||||
                         0x08,            // byte 7 = msg_type_id
 | 
					                         0x08,            // byte 7 = msg_type_id
 | 
				
			||||||
                         1,    0, 0, 0,   // bytes 8-11 = msg_stream_id = 1
 | 
					                         1,    0, 0, 0,   // bytes 8-11 = msg_stream_id = 1
 | 
				
			||||||
                         0,    0, 0, 0};  // bytes 12-15 = extended timestamp
 | 
					                         0,    0, 0, 0};  // bytes 12-15 = extended timestamp
 | 
				
			||||||
 | 
					                         
 | 
				
			||||||
    bool allow_short = RTMPStream::lastsend.count(4);
 | 
					    bool allow_short = RTMPStream::lastsend.count(4);
 | 
				
			||||||
    RTMPStream::Chunk &prev = RTMPStream::lastsend[4];
 | 
					    RTMPStream::Chunk &prev = RTMPStream::lastsend[4];
 | 
				
			||||||
    uint8_t chtype = 0x00;
 | 
					    uint8_t chtype = 0x00;
 | 
				
			||||||
| 
						 | 
					@ -340,6 +362,164 @@ namespace Mist{
 | 
				
			||||||
    myConn.setBlocking(false);
 | 
					    myConn.setBlocking(false);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  // Gets next ADTS frame and loops back to 0 is EOF is reached
 | 
				
			||||||
 | 
					  void OutRTMP::calcNextFrameInfo(){ 
 | 
				
			||||||
 | 
					    // Set iterator to start of next frame
 | 
				
			||||||
 | 
					    customAudioIterator += currentFrameInfo.getCompleteSize();
 | 
				
			||||||
 | 
					    // Loop the audio
 | 
				
			||||||
 | 
					    if (customAudioIterator >= customAudioSize)
 | 
				
			||||||
 | 
					      customAudioIterator = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Confirm syncword (= FFF)
 | 
				
			||||||
 | 
					    if (customAudioFile[customAudioIterator] != 0xFF || ( customAudioFile[customAudioIterator + 1] & 0xF0) != 0xF0 ){
 | 
				
			||||||
 | 
					      WARN_MSG("Invalid sync word at start of header. Will probably read garbage...");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint64_t frameSize = (((customAudioFile[customAudioIterator + 3] & 0x03) << 11) 
 | 
				
			||||||
 | 
					                        | ( customAudioFile[customAudioIterator + 4] << 3)
 | 
				
			||||||
 | 
					                        |(( customAudioFile[customAudioIterator + 5] >> 5) & 0x07));
 | 
				
			||||||
 | 
					    aac::adts adtsPack(customAudioFile + customAudioIterator, frameSize);
 | 
				
			||||||
 | 
					    if (!adtsPack){
 | 
				
			||||||
 | 
					      WARN_MSG("Could not parse ADTS package. Will probably read garbage..."); 
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // Update internal variables
 | 
				
			||||||
 | 
					    currentFrameInfo = adtsPack;
 | 
				
			||||||
 | 
					    currentFrameTimestamp += (adtsPack.getSampleCount() * 1000) / adtsPack.getFrequency();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Sends FLV audio tag + raw AAC data
 | 
				
			||||||
 | 
					  void OutRTMP::sendLoopedAudio(uint64_t untilTimestamp){
 | 
				
			||||||
 | 
					    // ADTS frame can be invalid if there is metadata or w/e in the input file
 | 
				
			||||||
 | 
					    if ( !currentFrameInfo ){
 | 
				
			||||||
 | 
					      if (customAudioIterator == 0){
 | 
				
			||||||
 | 
					        ERROR_MSG("Input .AAC file is invalid!");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // Re-init currentFrameInfo
 | 
				
			||||||
 | 
					      WARN_MSG("File contains invalid ADTS frame. Resetting filePos to 0 and throwing this data...");
 | 
				
			||||||
 | 
					      customAudioSize = customAudioIterator;
 | 
				
			||||||
 | 
					      customAudioIterator = 0;
 | 
				
			||||||
 | 
					      // NOTE that we do not reset the timestamp to prevent eternal loops
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      // Confirm syncword (= FFF)
 | 
				
			||||||
 | 
					      if (customAudioFile[customAudioIterator] != 0xFF || ( customAudioFile[customAudioIterator + 1] & 0xF0) != 0xF0 ){
 | 
				
			||||||
 | 
					        WARN_MSG("Invalid sync word at start of header. Invalid input file!");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      uint64_t frameSize = (((customAudioFile[customAudioIterator + 3] & 0x03) << 11) | ( customAudioFile[customAudioIterator + 4] << 3) | (( customAudioFile[customAudioIterator + 5] >> 5) & 0x07));
 | 
				
			||||||
 | 
					      aac::adts adtsPack(customAudioFile + customAudioIterator, frameSize);
 | 
				
			||||||
 | 
					      if (!adtsPack){
 | 
				
			||||||
 | 
					        WARN_MSG("Could not parse ADTS package. Invalid input file!");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      currentFrameInfo = adtsPack;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Keep parsing ADTS frames until we reach a frame which starts in the future
 | 
				
			||||||
 | 
					    while (currentFrameTimestamp < untilTimestamp){  
 | 
				
			||||||
 | 
					      // Init RTMP header info
 | 
				
			||||||
 | 
					      char rtmpheader[] ={0,              // byte 0 = cs_id | ch_type
 | 
				
			||||||
 | 
					                          0,    0, 0,     // bytes 1-3 = timestamp
 | 
				
			||||||
 | 
					                          0,    0, 0,     // bytes 4-6 = length
 | 
				
			||||||
 | 
					                          0x08,           // byte 7 = msg_type_id
 | 
				
			||||||
 | 
					                          1,    0, 0, 0,  // bytes 8-11 = msg_stream_id = 1
 | 
				
			||||||
 | 
					                          0,    0, 0, 0}; // bytes 12-15 = extended timestamp
 | 
				
			||||||
 | 
					                          
 | 
				
			||||||
 | 
					      // Separate timestamp since we store Δtimestamps
 | 
				
			||||||
 | 
					      uint64_t rtmpTimestamp = currentFrameTimestamp;
 | 
				
			||||||
 | 
					      // Since we have to prepend an FLV audio tag, increase size by 2 bytes
 | 
				
			||||||
 | 
					      uint64_t aacPacketSize = currentFrameInfo.getPayloadSize() + 2;
 | 
				
			||||||
 | 
					      // If there is a previous sent package, we do not need to send all data
 | 
				
			||||||
 | 
					      bool allow_short = RTMPStream::lastsend.count(4);
 | 
				
			||||||
 | 
					      RTMPStream::Chunk &prev = RTMPStream::lastsend[4];
 | 
				
			||||||
 | 
					      // Defines the type of header. Only the 2 most significant bits are counted:
 | 
				
			||||||
 | 
					      //  0x00 = 000.. = 12 byte header
 | 
				
			||||||
 | 
					      //  0x40 = 010.. = 8 byte header, leave out message ID if it's the same as prev
 | 
				
			||||||
 | 
					      //  0x80 = 100.. = 4 byte header, above + leave out msg type and size if the packets are all the same size and type
 | 
				
			||||||
 | 
					      //  0xC0 = 110.. = 1 byte header, above + leave out timestamp as well
 | 
				
			||||||
 | 
					      uint8_t chtype = 0x00;
 | 
				
			||||||
 | 
					      size_t header_len = 12;
 | 
				
			||||||
 | 
					      bool time_is_diff = false;
 | 
				
			||||||
 | 
					      if (allow_short && (prev.cs_id == 4)){
 | 
				
			||||||
 | 
					        if (prev.msg_stream_id == 1){
 | 
				
			||||||
 | 
					          chtype = 0x40;
 | 
				
			||||||
 | 
					          header_len = 8;
 | 
				
			||||||
 | 
					          if (aacPacketSize == prev.len && rtmpheader[7] == prev.msg_type_id){
 | 
				
			||||||
 | 
					            chtype = 0x80;
 | 
				
			||||||
 | 
					            header_len = 4;
 | 
				
			||||||
 | 
					            if (rtmpTimestamp == prev.timestamp){
 | 
				
			||||||
 | 
					              chtype = 0xC0;
 | 
				
			||||||
 | 
					              header_len = 1;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          // override - we always sent type 0x00 if the timestamp has decreased since last chunk in this channel
 | 
				
			||||||
 | 
					          if (rtmpTimestamp < prev.timestamp){
 | 
				
			||||||
 | 
					            chtype = 0x00;
 | 
				
			||||||
 | 
					            header_len = 12;
 | 
				
			||||||
 | 
					          }else{
 | 
				
			||||||
 | 
					            // store the timestamp diff instead of the whole timestamp
 | 
				
			||||||
 | 
					            rtmpTimestamp -= prev.timestamp;
 | 
				
			||||||
 | 
					            time_is_diff = true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Update previous chunk variables
 | 
				
			||||||
 | 
					      prev.cs_id = 4;
 | 
				
			||||||
 | 
					      prev.msg_stream_id = 1;
 | 
				
			||||||
 | 
					      prev.len = aacPacketSize;
 | 
				
			||||||
 | 
					      prev.msg_type_id = 0x08;
 | 
				
			||||||
 | 
					      if (time_is_diff){
 | 
				
			||||||
 | 
					        prev.timestamp += rtmpTimestamp;
 | 
				
			||||||
 | 
					      }else{
 | 
				
			||||||
 | 
					        prev.timestamp = rtmpTimestamp;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Now fill in type...
 | 
				
			||||||
 | 
					      rtmpheader[0] = chtype | 4;
 | 
				
			||||||
 | 
					      // data length...
 | 
				
			||||||
 | 
					      rtmpheader[4] = (aacPacketSize >> 16) & 0xff;
 | 
				
			||||||
 | 
					      rtmpheader[5] = (aacPacketSize >> 8) & 0xff;
 | 
				
			||||||
 | 
					      rtmpheader[6] = aacPacketSize & 0xff;
 | 
				
			||||||
 | 
					      // and timestamp (3 bytes unless extended)
 | 
				
			||||||
 | 
					      if (rtmpTimestamp >= 0x00ffffff){
 | 
				
			||||||
 | 
					        rtmpheader[1] = 0xff;
 | 
				
			||||||
 | 
					        rtmpheader[2] = 0xff;
 | 
				
			||||||
 | 
					        rtmpheader[3] = 0xff;
 | 
				
			||||||
 | 
					        rtmpheader[header_len++] = (rtmpTimestamp >> 24) & 0xff;
 | 
				
			||||||
 | 
					        rtmpheader[header_len++] = (rtmpTimestamp >> 16) & 0xff;
 | 
				
			||||||
 | 
					        rtmpheader[header_len++] = (rtmpTimestamp >> 8) & 0xff;
 | 
				
			||||||
 | 
					        rtmpheader[header_len++] = rtmpTimestamp & 0xff;
 | 
				
			||||||
 | 
					      }else{
 | 
				
			||||||
 | 
					        rtmpheader[1] = (rtmpTimestamp >> 16) & 0xff;
 | 
				
			||||||
 | 
					        rtmpheader[2] = (rtmpTimestamp >> 8) & 0xff;
 | 
				
			||||||
 | 
					        rtmpheader[3] = rtmpTimestamp & 0xff;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Send RTMP packet containing header only
 | 
				
			||||||
 | 
					      myConn.setBlocking(true);
 | 
				
			||||||
 | 
					      myConn.SendNow(rtmpheader, header_len);
 | 
				
			||||||
 | 
					      // Prepend FLV AAC audio tag
 | 
				
			||||||
 | 
					      char *tmpData = (char*)malloc(aacPacketSize);
 | 
				
			||||||
 | 
					      const char *tmpBuf = currentFrameInfo.getPayload();
 | 
				
			||||||
 | 
					      // Prepend FLV Audio tag: always 10101111 00000001 + raw AAC
 | 
				
			||||||
 | 
					      tmpData[0] = '\257';
 | 
				
			||||||
 | 
					      tmpData[1] = '\001';
 | 
				
			||||||
 | 
					      for (int i = 2; i < aacPacketSize; i++)
 | 
				
			||||||
 | 
					        tmpData[i] = tmpBuf[i-2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      myConn.SendNow(tmpData, aacPacketSize);
 | 
				
			||||||
 | 
					      // Update internal variables
 | 
				
			||||||
 | 
					      RTMPStream::snd_cnt += header_len+aacPacketSize;
 | 
				
			||||||
 | 
					      myConn.setBlocking(false);
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      // get next ADTS frame for new raw AAC data
 | 
				
			||||||
 | 
					      calcNextFrameInfo();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
  void OutRTMP::sendNext(){
 | 
					  void OutRTMP::sendNext(){
 | 
				
			||||||
    //Every 5s, check if the track selection should change in live streams, and do it.
 | 
					    //Every 5s, check if the track selection should change in live streams, and do it.
 | 
				
			||||||
    if (M.getLive()){
 | 
					    if (M.getLive()){
 | 
				
			||||||
| 
						 | 
					@ -374,20 +554,33 @@ namespace Mist{
 | 
				
			||||||
    // Send silence packets if needed
 | 
					    // Send silence packets if needed
 | 
				
			||||||
    if (hasSilence){
 | 
					    if (hasSilence){
 | 
				
			||||||
      // If there's more than 15s of skip, skip audio as well
 | 
					      // If there's more than 15s of skip, skip audio as well
 | 
				
			||||||
      if (timestamp > 15000 && lastSilence < timestamp - 15000){
 | 
					      if (timestamp > 15000 && lastAudioInserted < timestamp - 15000){
 | 
				
			||||||
        lastSilence = timestamp - 30;
 | 
					        lastAudioInserted = timestamp - 30;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // convert time to packet counter
 | 
					      // convert time to packet counter
 | 
				
			||||||
      uint64_t currSilence = ((lastSilence*44100+512000)/1024000)+1;
 | 
					      uint64_t currSilence = ((lastAudioInserted*44100+512000)/1024000)+1;
 | 
				
			||||||
      uint64_t silentTime = currSilence*1024000/44100;
 | 
					      uint64_t silentTime = currSilence*1024000/44100;
 | 
				
			||||||
      // keep sending silent packets until we've caught up to the current timestamp
 | 
					      // keep sending silent packets until we've caught up to the current timestamp
 | 
				
			||||||
      while (silentTime < timestamp){
 | 
					      while (silentTime < timestamp){
 | 
				
			||||||
        sendSilence(silentTime);
 | 
					        sendSilence(silentTime);
 | 
				
			||||||
        lastSilence = silentTime;
 | 
					        lastAudioInserted = silentTime;
 | 
				
			||||||
        silentTime = (++currSilence)*1024000/44100;
 | 
					        silentTime = (++currSilence)*1024000/44100;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // NOTE hier ergens fixen dat de audio ook stopt als video wegvalt
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Send looped audio if needed
 | 
				
			||||||
 | 
					    if (hasCustomAudio){
 | 
				
			||||||
 | 
					      // If there's more than 15s of skip, skip audio as well
 | 
				
			||||||
 | 
					      if (timestamp > 15000 && lastAudioInserted < timestamp - 15000){
 | 
				
			||||||
 | 
					        lastAudioInserted = timestamp - 30;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // keep sending silent packets until we've caught up to the current timestamp
 | 
				
			||||||
 | 
					      sendLoopedAudio(timestamp);
 | 
				
			||||||
 | 
					      lastAudioInserted = timestamp;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    char rtmpheader[] ={ 0,              // byte 0 = cs_id | ch_type
 | 
					    char rtmpheader[] ={ 0,              // byte 0 = cs_id | ch_type
 | 
				
			||||||
                         0,    0, 0,     // bytes 1-3 = timestamp
 | 
					                         0,    0, 0,     // bytes 1-3 = timestamp
 | 
				
			||||||
                         0,    0, 0,     // bytes 4-6 = length
 | 
					                         0,    0, 0,     // bytes 4-6 = length
 | 
				
			||||||
| 
						 | 
					@ -425,6 +618,7 @@ namespace Mist{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (type == "audio"){
 | 
					    if (type == "audio"){
 | 
				
			||||||
      uint32_t rate = M.getRate(thisIdx);
 | 
					      uint32_t rate = M.getRate(thisIdx);
 | 
				
			||||||
 | 
					      WARN_MSG("Rate=%i", rate);
 | 
				
			||||||
      rtmpheader[7] = 0x08;
 | 
					      rtmpheader[7] = 0x08;
 | 
				
			||||||
      if (codec == "AAC"){
 | 
					      if (codec == "AAC"){
 | 
				
			||||||
        dataheader[0] += 0xA0;
 | 
					        dataheader[0] += 0xA0;
 | 
				
			||||||
| 
						 | 
					@ -570,6 +764,18 @@ namespace Mist{
 | 
				
			||||||
  void OutRTMP::sendHeader(){
 | 
					  void OutRTMP::sendHeader(){
 | 
				
			||||||
    FLV::Tag tag;
 | 
					    FLV::Tag tag;
 | 
				
			||||||
    std::set<size_t> selectedTracks;
 | 
					    std::set<size_t> selectedTracks;
 | 
				
			||||||
 | 
					    // Will contain the full audio=<> parameter in it, which should be CSV's of
 | 
				
			||||||
 | 
					    // {path, url, filename, silent, silence}1..*
 | 
				
			||||||
 | 
					    std::string audioParameterBuffer;
 | 
				
			||||||
 | 
					    // Current parameter we're parsing
 | 
				
			||||||
 | 
					    std::string audioParameter;
 | 
				
			||||||
 | 
					    // Indicates position where the previous parameter ended
 | 
				
			||||||
 | 
					    int prevPos = 0;
 | 
				
			||||||
 | 
					    // Used to read a custom AAC file 
 | 
				
			||||||
 | 
					    HTTP::URIReader inAAC;
 | 
				
			||||||
 | 
					    char *tempBuffer;
 | 
				
			||||||
 | 
					    size_t bytesRead;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
 | 
					    for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
 | 
				
			||||||
      selectedTracks.insert(it->first);
 | 
					      selectedTracks.insert(it->first);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -594,13 +800,92 @@ namespace Mist{
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    //Insert silent init data if audio set to silent
 | 
					    // Insert silent init data if audio set to silent or loop a custom AAC file
 | 
				
			||||||
    hasSilence = (targetParams.count("audio") && targetParams["audio"] == "silent");
 | 
					    audioParameterBuffer = targetParams["audio"];
 | 
				
			||||||
 | 
					    HIGH_MSG("audioParameterBuffer: %s", audioParameterBuffer.c_str());
 | 
				
			||||||
 | 
					    // Read until we find a , or end of audioParameterBuffer
 | 
				
			||||||
 | 
					    for (std::string::size_type i = 0; i < audioParameterBuffer.size(); i++){
 | 
				
			||||||
 | 
					      if ( (audioParameterBuffer[i] == ',') || i + 1 == (audioParameterBuffer.size()) ){
 | 
				
			||||||
 | 
					        // If end of buffer reached, take entire string
 | 
				
			||||||
 | 
					        if (i + 1 == audioParameterBuffer.size()){i++;}
 | 
				
			||||||
 | 
					        // Get audio parameter
 | 
				
			||||||
 | 
					        audioParameter = audioParameterBuffer.substr(prevPos, i - prevPos);
 | 
				
			||||||
 | 
					        HIGH_MSG("Parsing audio parameter %s", audioParameter.c_str());
 | 
				
			||||||
 | 
					        // Inc i to skip the ,
 | 
				
			||||||
 | 
					        i++; 
 | 
				
			||||||
 | 
					        prevPos = i;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if (audioParameter == "silence" || audioParameter == "silent"){
 | 
				
			||||||
 | 
					          hasSilence = true;
 | 
				
			||||||
 | 
					          INFO_MSG("Filling audio track with silence");
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // Else parse AAC track(s) until we find one which works for us
 | 
				
			||||||
 | 
					        else{
 | 
				
			||||||
 | 
					          inAAC.open(audioParameter);
 | 
				
			||||||
 | 
					          if (inAAC && !inAAC.isEOF()){
 | 
				
			||||||
 | 
					              inAAC.readAll(tempBuffer, bytesRead);
 | 
				
			||||||
 | 
					              customAudioSize = bytesRead;
 | 
				
			||||||
 | 
					              // Copy to buffer since inAAC will be closed soon...
 | 
				
			||||||
 | 
					              customAudioFile = (char*)malloc(bytesRead);
 | 
				
			||||||
 | 
					              memcpy(customAudioFile, tempBuffer, bytesRead);
 | 
				
			||||||
 | 
					              hasCustomAudio = true;
 | 
				
			||||||
 | 
					              customAudioIterator = 0;
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          else{
 | 
				
			||||||
 | 
					            INFO_MSG("Could not parse audio parameter %s. Skipping...", audioParameter.c_str());
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    if (hasSilence && tag.DTSCAudioInit("AAC", 44100, 32, 2, std::string("\022\020V\345\000", 5))){
 | 
					    if (hasSilence && tag.DTSCAudioInit("AAC", 44100, 32, 2, std::string("\022\020V\345\000", 5))){
 | 
				
			||||||
 | 
					      // InitData contains AudioSpecificConfig:
 | 
				
			||||||
 | 
					      //                   \022       \020       V        \345     \000
 | 
				
			||||||
 | 
					      //   12 10 56 e5 0 = 00010-010  0-0010-000 01010110 11100101 00000000
 | 
				
			||||||
 | 
					      //                   Type -sample-chnl-000
 | 
				
			||||||
 | 
					      //                   AACLC-44100 -2   -000
 | 
				
			||||||
      INFO_MSG("Inserting silence track init data");
 | 
					      INFO_MSG("Inserting silence track init data");
 | 
				
			||||||
      tag.tagTime(currentTime());
 | 
					      tag.tagTime(currentTime());
 | 
				
			||||||
      myConn.SendNow(RTMPStream::SendMedia(tag));
 | 
					      myConn.SendNow(RTMPStream::SendMedia(tag));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (hasCustomAudio){
 | 
				
			||||||
 | 
					      // Get first frame in order to init the audio track correctly
 | 
				
			||||||
 | 
					      // Confirm syncword (= FFF)
 | 
				
			||||||
 | 
					      if (customAudioFile[customAudioIterator] != 0xFF || ( customAudioFile[customAudioIterator + 1] & 0xF0) != 0xF0 ){
 | 
				
			||||||
 | 
					        WARN_MSG("Invalid sync word at start of header. Invalid input file!");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // Calculate the starting position of the next frame
 | 
				
			||||||
 | 
					      uint64_t frameSize = (((customAudioFile[customAudioIterator + 3] & 0x03) << 11) | ( customAudioFile[customAudioIterator + 4] << 3) | (( customAudioFile[customAudioIterator + 5] >> 5) & 0x07));
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					      // Create ADTS object of frame
 | 
				
			||||||
 | 
					      aac::adts adtsPack(customAudioFile + customAudioIterator, frameSize);
 | 
				
			||||||
 | 
					      if (!adtsPack){
 | 
				
			||||||
 | 
					        WARN_MSG("Could not parse ADTS package. Invalid input file!");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      currentFrameInfo = adtsPack;
 | 
				
			||||||
 | 
					      char *tempInitData = (char*)malloc(2);
 | 
				
			||||||
 | 
					      /* 
 | 
				
			||||||
 | 
					       * Create AudioSpecificConfig
 | 
				
			||||||
 | 
					       * DTSCAudioInit already includes the sequence header at pos 12
 | 
				
			||||||
 | 
					       * We need:
 | 
				
			||||||
 | 
					       *  objectType       = getAACProfile (5 bits) (probably 00001 AAC Main or 00010 AACLC)
 | 
				
			||||||
 | 
					       *  Sampling Rate    = 44100    = 0100
 | 
				
			||||||
 | 
					       *  Channels         = 2        = 0010
 | 
				
			||||||
 | 
					       *  + 000
 | 
				
			||||||
 | 
					       */
 | 
				
			||||||
 | 
					      tempInitData[0] = 0x02 + (currentFrameInfo.getAACProfile() << 3);
 | 
				
			||||||
 | 
					      tempInitData[1] = 0x10;
 | 
				
			||||||
 | 
					      const std::string initData = std::string(tempInitData, 2);
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      if (tag.DTSCAudioInit("AAC", currentFrameInfo.getFrequency(), currentFrameInfo.getSampleCount(), currentFrameInfo.getChannelCount(), initData)){
 | 
				
			||||||
 | 
					        INFO_MSG("Loaded a %" PRIu64 " byte custom audio file as audio loop", customAudioSize);
 | 
				
			||||||
 | 
					        myConn.SendNow(RTMPStream::SendMedia(tag));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sentHeader = true;
 | 
					    sentHeader = true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,8 @@
 | 
				
			||||||
#include <mist/http_parser.h>
 | 
					#include <mist/http_parser.h>
 | 
				
			||||||
#include <mist/rtmpchunks.h>
 | 
					#include <mist/rtmpchunks.h>
 | 
				
			||||||
#include <mist/url.h>
 | 
					#include <mist/url.h>
 | 
				
			||||||
 | 
					#include <mist/urireader.h>
 | 
				
			||||||
 | 
					#include <mist/adts.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Mist{
 | 
					namespace Mist{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,6 +37,21 @@ namespace Mist{
 | 
				
			||||||
    void sendSilence(uint64_t currTime);
 | 
					    void sendSilence(uint64_t currTime);
 | 
				
			||||||
    bool hasSilence;
 | 
					    bool hasSilence;
 | 
				
			||||||
    uint64_t lastSilence;
 | 
					    uint64_t lastSilence;
 | 
				
			||||||
 | 
					    // Indicates whether the track should loop a custom audio file instead of normal audio
 | 
				
			||||||
 | 
					    bool hasCustomAudio;
 | 
				
			||||||
 | 
					    // Last timestamp we inserted custom audio / silence
 | 
				
			||||||
 | 
					    uint64_t lastAudioInserted;
 | 
				
			||||||
 | 
					    // Info on AAC file (including headers and such)
 | 
				
			||||||
 | 
					    uint64_t customAudioSize;
 | 
				
			||||||
 | 
					    uint64_t customAudioIterator;
 | 
				
			||||||
 | 
					    char *customAudioFile;
 | 
				
			||||||
 | 
					    // Info on current ADTS frame
 | 
				
			||||||
 | 
					    aac::adts currentFrameInfo;
 | 
				
			||||||
 | 
					    uint64_t currentFrameTimestamp;
 | 
				
			||||||
 | 
					    // Loops .AAC file contents until untilTimestamp is reached
 | 
				
			||||||
 | 
					    void sendLoopedAudio(uint64_t untilTimestamp);
 | 
				
			||||||
 | 
					    // Gets the next ADTS frame in AAC file. Loops if EOF reached
 | 
				
			||||||
 | 
					    void calcNextFrameInfo();
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}// namespace Mist
 | 
					}// namespace Mist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue