diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index 92929b92..e354dc56 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -12,6 +12,7 @@ extern "C" { #include +#include #include } @@ -79,11 +80,21 @@ struct audio_packet_raw_t { RTP_PACKET rtp; }; +struct audio_fec_packet_raw_t { + uint8_t *payload() { + return (uint8_t *)(this + 1); + } + + RTP_PACKET rtp; + AUDIO_FEC_HEADER fecHeader; +}; + #pragma pack(pop) using rh_t = util::safe_ptr; using video_packet_t = util::c_ptr; using audio_packet_t = util::c_ptr; +using audio_fec_packet_t = util::c_ptr; using message_queue_t = std::shared_ptr>>; using message_queue_queue_t = std::shared_ptr>>; @@ -181,7 +192,8 @@ struct session_t { } video; struct { - std::uint16_t frame; + std::uint16_t sequenceNumber; + std::uint32_t timestamp; udp::endpoint peer; } audio; @@ -709,6 +721,39 @@ void audioBroadcastThread(udp::socket &sock) { auto shutdown_event = mail::man->event(mail::broadcast_shutdown); auto packets = mail::man->queue(mail::audio_packets); + auto max_block_size = 2048; + util::buffer_t shards { RTPA_TOTAL_SHARDS * 2048 }; + util::buffer_t shards_p { RTPA_TOTAL_SHARDS }; + + for(auto x = 0; x < RTPA_TOTAL_SHARDS; ++x) { + shards_p[x] = (uint8_t *)&shards[x * max_block_size]; + } + + audio_packet_t audio_packet { (audio_packet_raw_t *)malloc(sizeof(audio_packet_raw_t) + max_block_size) }; + audio_fec_packet_t audio_fec_packet { (audio_fec_packet_raw_t *)malloc(sizeof(audio_fec_packet_raw_t) + max_block_size) }; + fec::rs_t rs { reed_solomon_new(RTPA_DATA_SHARDS, RTPA_FEC_SHARDS) }; + + // For unknown reasons, the RS parity matrix computed by our RS implementation + // doesn't match the one Nvidia uses for audio data. I'm not exactly sure why, + // but we can simply replace it with the matrix generated by OpenFEC which + // works correctly. This is possible because the data and FEC shard count is + // constant and known in advance. + const unsigned char parity[] = { 0x77, 0x40, 0x38, 0x0e, 0xc7, 0xa7, 0x0d, 0x6c }; + memcpy(&rs.get()->m[16], parity, sizeof(parity)); + memcpy(rs.get()->parity, parity, sizeof(parity)); + + audio_packet->rtp.header = 0x80; + audio_packet->rtp.packetType = 97; + audio_packet->rtp.ssrc = 0; + + audio_fec_packet->rtp.header = 0x80; + audio_fec_packet->rtp.packetType = 127; + audio_fec_packet->rtp.timestamp = 0; + audio_fec_packet->rtp.ssrc = 0; + + audio_fec_packet->fecHeader.payloadType = audio_packet->rtp.packetType; + audio_fec_packet->fecHeader.ssrc = audio_packet->rtp.ssrc; + while(auto packet = packets->pop()) { if(shutdown_event->peek()) { break; @@ -717,20 +762,39 @@ void audioBroadcastThread(udp::socket &sock) { TUPLE_2D_REF(channel_data, packet_data, *packet); auto session = (session_t *)channel_data; - auto frame = session->audio.frame++; + auto sequenceNumber = session->audio.sequenceNumber; + auto timestamp = session->audio.timestamp; - audio_packet_t audio_packet { (audio_packet_raw_t *)malloc(sizeof(audio_packet_raw_t) + packet_data.size()) }; + audio_packet->rtp.sequenceNumber = util::endian::big(sequenceNumber); + audio_packet->rtp.timestamp = util::endian::big(timestamp); - audio_packet->rtp.header = 0; - audio_packet->rtp.packetType = 97; - audio_packet->rtp.sequenceNumber = util::endian::big(frame); - audio_packet->rtp.timestamp = 0; - audio_packet->rtp.ssrc = 0; + session->audio.sequenceNumber++; + session->audio.timestamp += session->config.audio.packetDuration; std::copy(std::begin(packet_data), std::end(packet_data), audio_packet->payload()); + std::copy(std::begin(packet_data), std::end(packet_data), shards_p[sequenceNumber % RTPA_DATA_SHARDS]); sock.send_to(asio::buffer((char *)audio_packet.get(), sizeof(audio_packet_raw_t) + packet_data.size()), session->audio.peer); - BOOST_LOG(verbose) << "Audio ["sv << frame << "] :: send..."sv; + BOOST_LOG(verbose) << "Audio ["sv << sequenceNumber << "] :: send..."sv; + + // initialize the FEC header at the beginning of the FEC block + if (sequenceNumber % RTPA_DATA_SHARDS == 0) { + audio_fec_packet->fecHeader.baseSequenceNumber = util::endian::big(sequenceNumber); + audio_fec_packet->fecHeader.baseTimestamp = util::endian::big(timestamp); + } + + // generate parity shards at the end of the FEC block + if ((sequenceNumber + 1) % RTPA_DATA_SHARDS == 0) { + reed_solomon_encode(rs.get(), shards_p.begin(), RTPA_TOTAL_SHARDS, packet_data.size()); + + for(auto x = 0; x < RTPA_FEC_SHARDS; ++x) { + audio_fec_packet->rtp.sequenceNumber = util::endian::big(sequenceNumber + x + 1); + audio_fec_packet->fecHeader.fecShardIndex = x; + memcpy(audio_fec_packet->payload(), shards_p[RTPA_DATA_SHARDS + x], packet_data.size()); + sock.send_to(asio::buffer((char *)audio_fec_packet.get(), sizeof(audio_fec_packet_raw_t) + packet_data.size()), session->audio.peer); + BOOST_LOG(verbose) << "Audio FEC ["sv << (sequenceNumber & ~(RTPA_DATA_SHARDS - 1)) << ' ' << x <<"] :: send..."sv; + } + } } shutdown_event->raise(true); @@ -958,7 +1022,8 @@ std::shared_ptr alloc(config_t &config, crypto::aes_t &gcm_key, crypt session->video.idr_events = mail->event(mail::idr); session->video.lowseq = 0; - session->audio.frame = 1; + session->audio.sequenceNumber = 0; + session->audio.timestamp = 0; session->control.peer = nullptr; session->state.store(state_e::STOPPED, std::memory_order_relaxed);