├── .gitignore ├── Makefile ├── README.md ├── ffmpeg-demux.cpp └── self-demux.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | self-demux: 2 | g++ -std=c++17 self-demux.cpp `pkg-config --libs --cflags libavcodec libavformat libavutil` -ldatachannel -rpath /usr/local/lib && ./a.out 3 | 4 | ffmpeg-demux: 5 | g++ -std=c++17 ffmpeg-demux.cpp `pkg-config --libs --cflags libavcodec libavformat libavutil` && ./a.out 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | An example of decoding RTP packets from an in memory source. 2 | 3 | I created this to support WebRTC in OBS. 4 | 5 | This code comes from https://blog.kevmo314.com/custom-rtp-io-with-ffmpeg.html 6 | -------------------------------------------------------------------------------- /ffmpeg-demux.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | extern "C" { 10 | #include "libavcodec/avcodec.h" 11 | #include "libavformat/avformat.h" 12 | #include "libavformat/avio.h" 13 | #include "libavutil/mem.h" 14 | } 15 | 16 | const auto rtp_buff_size = 1500; 17 | const auto static_session_description = 18 | "v=0\r\n" 19 | "o=- 0 0 IN IP4 127.0.0.1\r\n" 20 | "c=IN IP4 127.0.0.1\r\n" 21 | "m=video 5000 RTP/AVP 96\r\n" 22 | "a=rtpmap:96 H264/90000\r\n" 23 | "a=fmtp:96\r\n" 24 | "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f'" 25 | "\r\n"; 26 | 27 | std::vector rtp_file_list; 28 | 29 | void populate_rtp_buffer_list() { 30 | for (const auto &entry : 31 | std::filesystem::directory_iterator("/Users/sean/rtp-pkts")) { 32 | rtp_file_list.push_back(entry.path()); 33 | } 34 | std::sort(rtp_file_list.begin(), rtp_file_list.end()); 35 | } 36 | 37 | std::string print_av_error(int err) { 38 | char buff[500] = {0}; 39 | av_strerror(err, buff, 500); 40 | return std::string(buff); 41 | } 42 | 43 | AVIOContext *create_session_description_avio_context(bool *have_read) { 44 | auto avio_context = avio_alloc_context( 45 | reinterpret_cast(av_strdup(static_session_description)), 46 | strlen(static_session_description), 0, have_read, 47 | [](void *opaque, uint8_t *buf, int buf_size) -> int { 48 | auto have_read = static_cast(opaque); 49 | if (*have_read == true) { 50 | return AVERROR_EOF; 51 | } 52 | 53 | strncpy(reinterpret_cast(buf), static_session_description, 54 | buf_size); 55 | *have_read = true; 56 | return strlen(static_session_description); 57 | }, 58 | NULL, NULL); 59 | 60 | if (avio_context == nullptr) { 61 | throw std::runtime_error("Failed to create avio_context"); 62 | } 63 | 64 | return avio_context; 65 | } 66 | 67 | AVIOContext *create_rtp_avio_context(int *rtp_file_index) { 68 | auto avio_context = avio_alloc_context( 69 | static_cast(av_malloc(rtp_buff_size)), rtp_buff_size, 1, 70 | rtp_file_index, 71 | [](void *opaque, uint8_t *buf, int buf_size) -> int { 72 | auto rtp_file_index = static_cast(opaque); 73 | 74 | if (*rtp_file_index >= rtp_file_list.size()) { 75 | return AVERROR_EOF; 76 | } 77 | 78 | auto fp = fopen(rtp_file_list[*rtp_file_index].c_str(), "rb"); 79 | auto amount_read = fread(buf, 1, rtp_buff_size, fp); 80 | fclose(fp); 81 | 82 | (*rtp_file_index)++; 83 | 84 | return amount_read; 85 | }, 86 | // Ignore RTCP Packets. Must be set 87 | [](void *, uint8_t *, int buf_size) -> int { return buf_size; }, NULL); 88 | 89 | if (avio_context == nullptr) { 90 | throw std::runtime_error("Failed to create avio_context"); 91 | } 92 | 93 | return avio_context; 94 | } 95 | 96 | int main() { 97 | populate_rtp_buffer_list(); 98 | bool have_read = false; 99 | int rtp_file_index = 0; 100 | auto session_description_avio_context = 101 | create_session_description_avio_context(&have_read); 102 | auto rtp_avio_context = create_rtp_avio_context(&rtp_file_index); 103 | 104 | auto avformat_context = avformat_alloc_context(); 105 | if (avformat_context == nullptr) { 106 | throw std::runtime_error("Failed to create avformat_context"); 107 | } 108 | avformat_context->pb = session_description_avio_context; 109 | 110 | AVDictionary *avformat_open_input_options = nullptr; 111 | av_dict_set(&avformat_open_input_options, "sdp_flags", "custom_io", 0); 112 | av_dict_set_int(&avformat_open_input_options, "reorder_queue_size", 0, 0); 113 | 114 | int status = 0; 115 | if ((status = avformat_open_input(&avformat_context, "", nullptr, 116 | &avformat_open_input_options)) != 0) { 117 | throw std::runtime_error("Failed to avformat_open_input " + 118 | print_av_error(status)); 119 | } 120 | avformat_context->pb = rtp_avio_context; 121 | 122 | if ((status = avformat_find_stream_info(avformat_context, nullptr)) != 0) { 123 | throw std::runtime_error("Failed to avformat_find_stream_info " + 124 | print_av_error(status)); 125 | } 126 | 127 | AVCodecContext *av_codec_context_audio = nullptr; 128 | AVCodecContext *av_codec_context_video = nullptr; 129 | 130 | for (int i = 0; i < avformat_context->nb_streams; i++) { 131 | auto avstream = avformat_context->streams[i]; 132 | auto decoder = avcodec_find_decoder(avstream->codecpar->codec_id); 133 | auto avcodec_context = av_codec_context_audio = 134 | avcodec_alloc_context3(decoder); 135 | 136 | if ((status = avcodec_open2(avcodec_context, decoder, NULL)) != 0) { 137 | throw std::runtime_error("Failed to avcodec_open2 " + 138 | print_av_error(status)); 139 | } 140 | 141 | if (avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { 142 | av_codec_context_audio = avcodec_context; 143 | } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { 144 | av_codec_context_video = avcodec_context; 145 | } 146 | } 147 | 148 | AVPacket packet; 149 | AVFrame *frame = av_frame_alloc(); 150 | while (av_read_frame(avformat_context, &packet) >= 0) { 151 | AVCodecContext *av_codec_context = NULL; 152 | auto *avstream = avformat_context->streams[packet.stream_index]; 153 | auto is_audio = avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO; 154 | 155 | if (is_audio) { 156 | av_codec_context = av_codec_context_audio; 157 | } else { 158 | av_codec_context = av_codec_context_video; 159 | } 160 | 161 | status = avcodec_send_packet(av_codec_context, &packet); 162 | if (status != AVERROR_INVALIDDATA && status != 0) { 163 | throw std::runtime_error("Failed to avcodec_send_packet " + 164 | print_av_error(status)); 165 | } 166 | 167 | status = avcodec_receive_frame(av_codec_context, frame); 168 | if (status != AVERROR(EAGAIN) && status != 0) { 169 | throw std::runtime_error("Failed to avcodec_receive_frame " + 170 | print_av_error(status)); 171 | } 172 | 173 | av_packet_unref(&packet); 174 | } 175 | 176 | avformat_free_context(avformat_context); 177 | avio_context_free(&session_description_avio_context); 178 | 179 | return 0; 180 | } 181 | 182 | -------------------------------------------------------------------------------- /self-demux.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | extern "C" { 13 | #include "libavcodec/avcodec.h" 14 | #include "libavformat/avformat.h" 15 | #include "libavformat/avio.h" 16 | #include "libavutil/mem.h" 17 | } 18 | 19 | const auto buff_size = 1024 * 1024 * 32; 20 | const auto naluTypeBitmask = std::byte(0x1F); 21 | const auto naluTypeSTAPA = std::byte(24); 22 | const auto naluTypeFUA = std::byte(28); 23 | const auto stapaHeaderSize = 1; 24 | const auto fuaHeaderSize = 2; 25 | const auto rtpHeaderSize = 12; 26 | 27 | const auto fuaEndBitmask = std::byte(0x40); 28 | const auto naluRefIdcBitmask = std::byte(0x60); 29 | 30 | std::vector> h264_frames; 31 | std::vector> rtp_pkts; 32 | std::vector fua_buffer; 33 | 34 | std::vector h264_nalu_header() 35 | { 36 | return std::vector{std::byte(0), std::byte(0), std::byte(0), 37 | std::byte(1)}; 38 | } 39 | 40 | void depacketize_h264() { 41 | auto pkt = rtp_pkts[0]; 42 | auto pktParsed = reinterpret_cast(pkt.data()); 43 | auto headerSize = rtpHeaderSize + pktParsed->csrcCount() + 44 | pktParsed->getExtensionHeaderSize(); 45 | auto naluType = pkt.at(headerSize) & naluTypeBitmask; 46 | 47 | if (naluType > std::byte(0) && naluType < std::byte(24)) { 48 | auto h264_nalu = h264_nalu_header(); 49 | std::copy(pkt.begin() + headerSize, pkt.end(), 50 | std::back_inserter(h264_nalu)); 51 | h264_frames.push_back(h264_nalu); 52 | } else if (naluType == naluTypeSTAPA) { 53 | auto currOffset = stapaHeaderSize + headerSize; 54 | while (currOffset < pkt.size()) { 55 | auto h264_nalu = h264_nalu_header(); 56 | 57 | auto naluSize = uint16_t(pkt.at(currOffset)) << 8 | uint8_t(pkt.at(currOffset + 1)); 58 | 59 | currOffset += 2; 60 | 61 | if (pkt.size() < currOffset + naluSize) { 62 | throw std::runtime_error( 63 | "STAP-A declared size is larger then buffer"); 64 | } 65 | 66 | std::copy(pkt.begin() + currOffset, 67 | pkt.begin() + currOffset + naluSize, 68 | std::back_inserter(h264_nalu)); 69 | currOffset += naluSize; 70 | 71 | h264_frames.push_back(h264_nalu); 72 | } 73 | } else if (naluType == naluTypeFUA) { 74 | if (fua_buffer.size() == 0) { 75 | fua_buffer = h264_nalu_header(); 76 | fua_buffer.push_back(std::byte(0)); 77 | } 78 | 79 | std::copy(pkt.begin() + headerSize + fuaHeaderSize, pkt.end(), 80 | std::back_inserter(fua_buffer)); 81 | 82 | if ((pkt.at(headerSize + 1) & fuaEndBitmask) != std::byte(0)) { 83 | auto naluRefIdc = pkt.at(headerSize) & 84 | naluRefIdcBitmask; 85 | auto fragmentedNaluType = pkt.at(headerSize + 1) & 86 | std::byte(naluTypeBitmask); 87 | 88 | fua_buffer[4] = naluRefIdc | fragmentedNaluType; 89 | 90 | h264_frames.push_back(fua_buffer); 91 | fua_buffer = std::vector{}; 92 | } 93 | } else { 94 | throw std::runtime_error("Unknown H264 RTP Packetization"); 95 | } 96 | } 97 | 98 | void populate_buffer_list() { 99 | char buffer[1500]; 100 | 101 | for (const auto &entry : 102 | std::filesystem::directory_iterator("/Users/sean/rtp-pkts")) { 103 | auto fd = fopen(entry.path().c_str(), "rb"); 104 | auto n = fread(buffer, sizeof(char), 1500, fd); 105 | fclose(fd); 106 | 107 | rtp_pkts.push_back(std::vector(reinterpret_cast(buffer), reinterpret_cast(buffer + n))); 108 | } 109 | 110 | std::sort(rtp_pkts.begin(), rtp_pkts.end(), 111 | [](auto const &pr1, auto const &pr2) { 112 | auto pkt1 = reinterpret_cast(pr1.data()); 113 | auto pkt2 = reinterpret_cast(pr2.data()); 114 | return pkt2->seqNumber() > pkt1->seqNumber(); 115 | }); 116 | 117 | while (true) { 118 | uint32_t currentTimestamp = 0; 119 | size_t i = 0; 120 | 121 | for (const auto &pkt : rtp_pkts) { 122 | auto p = reinterpret_cast(pkt.data()); 123 | 124 | if (i == 0) { 125 | currentTimestamp = p->timestamp(); 126 | } 127 | 128 | if (currentTimestamp != p->timestamp()) { 129 | break; 130 | } 131 | 132 | i++; 133 | } 134 | 135 | if (i == rtp_pkts.size()) { 136 | break; 137 | } else { 138 | for (auto j = 0; j < i; j++) { 139 | depacketize_h264(); 140 | rtp_pkts.erase(rtp_pkts.begin()); 141 | } 142 | } 143 | } 144 | } 145 | 146 | std::string print_av_error(int err) { 147 | char buff[500] = {0}; 148 | av_strerror(err, buff, 500); 149 | return std::string(buff); 150 | } 151 | 152 | AVIOContext *create_avio_context(int *h264_frame_index) { 153 | auto avio_context = avio_alloc_context( 154 | static_cast(av_malloc(buff_size)), buff_size, 0, 155 | h264_frame_index, 156 | [](void *opaque, uint8_t *buf, int buf_size) -> int { 157 | auto h264_frame_index = static_cast(opaque); 158 | 159 | if (*h264_frame_index >= h264_frames.size()) { 160 | return AVERROR_EOF; 161 | } 162 | 163 | std::memcpy(buf, h264_frames[*h264_frame_index].data(), 164 | h264_frames[*h264_frame_index].size()); 165 | 166 | (*h264_frame_index)++; 167 | 168 | return h264_frames[*h264_frame_index].size(); 169 | }, 170 | NULL, NULL); 171 | 172 | if (avio_context == nullptr) { 173 | throw std::runtime_error("Failed to create avio_context"); 174 | } 175 | 176 | return avio_context; 177 | } 178 | 179 | int main() { 180 | populate_buffer_list(); 181 | 182 | int h264_frame_index = 0; 183 | auto avio_context = create_avio_context(&h264_frame_index); 184 | 185 | auto avformat_context = avformat_alloc_context(); 186 | if (avformat_context == nullptr) { 187 | throw std::runtime_error("Failed to create avformat_context"); 188 | } 189 | avformat_context->pb = avio_context; 190 | 191 | int status = 0; 192 | if ((status = avformat_open_input(&avformat_context, "", nullptr, nullptr)) != 193 | 0) { 194 | throw std::runtime_error("Failed to avformat_open_input " + 195 | print_av_error(status)); 196 | } 197 | 198 | if ((status = avformat_find_stream_info(avformat_context, nullptr)) != 0) { 199 | throw std::runtime_error("Failed to avformat_find_stream_info " + 200 | print_av_error(status)); 201 | } 202 | 203 | AVCodecContext *av_codec_context_audio = nullptr; 204 | AVCodecContext *av_codec_context_video = nullptr; 205 | 206 | for (int i = 0; i < avformat_context->nb_streams; i++) { 207 | auto avstream = avformat_context->streams[i]; 208 | auto decoder = avcodec_find_decoder(avstream->codecpar->codec_id); 209 | auto avcodec_context = av_codec_context_audio = 210 | avcodec_alloc_context3(decoder); 211 | 212 | if ((status = avcodec_open2(avcodec_context, decoder, NULL)) != 0) { 213 | throw std::runtime_error("Failed to avcodec_open2 " + 214 | print_av_error(status)); 215 | } 216 | 217 | if (avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { 218 | av_codec_context_audio = avcodec_context; 219 | } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { 220 | av_codec_context_video = avcodec_context; 221 | } 222 | } 223 | 224 | AVPacket packet; 225 | AVFrame *frame = av_frame_alloc(); 226 | while (av_read_frame(avformat_context, &packet) >= 0) { 227 | AVCodecContext *av_codec_context = NULL; 228 | auto *avstream = avformat_context->streams[packet.stream_index]; 229 | auto is_audio = avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO; 230 | 231 | if (is_audio) { 232 | av_codec_context = av_codec_context_audio; 233 | } else { 234 | av_codec_context = av_codec_context_video; 235 | } 236 | 237 | status = avcodec_send_packet(av_codec_context, &packet); 238 | if (status != AVERROR_INVALIDDATA && status != 0) { 239 | throw std::runtime_error("Failed to avcodec_send_packet " + 240 | print_av_error(status)); 241 | } 242 | 243 | status = avcodec_receive_frame(av_codec_context, frame); 244 | if (status != AVERROR(EAGAIN) && status != 0) { 245 | throw std::runtime_error("Failed to avcodec_receive_frame " + 246 | print_av_error(status)); 247 | } 248 | 249 | av_packet_unref(&packet); 250 | } 251 | 252 | avformat_free_context(avformat_context); 253 | avio_context_free(&avio_context); 254 | 255 | return 0; 256 | } 257 | --------------------------------------------------------------------------------