H264编码RTP流SDP缺sprop-parameter-sets导致VLC无法播放问题
问题背景
我尝试将采集的摄像头帧编码为H264格式,通过RTP协议发送后在其他设备播放。目前发现FFmpeg为示例视频生成的SDP文件可同时被ffplay和VLC正常播放,自行生成的SDP文件仅能在ffplay播放,VLC无法正常加载播放,最终需要实现VLC可正常播放该RTP流。
测试情况
- ffplay可正常播放我生成的SDP对应的流
- mplayer播放时显示大黑色框内嵌绿色小框,疑似仅支持RTP封装mpegts格式,暂不深究
- VLC加载SDP后卡顿10秒,提示「无数据接收」后停止播放
可正常播放的SDP生成命令
使用如下FFmpeg命令生成的SDP文件可被双播放器正常播放:
ffmpeg -re -i some.mp4 -an -c:v copy -f rtp -sdp_file video.sdp "rtp://127.0.0.1:5004"
正常SDP内容
包含base64编码的sprop-parameter-sets字段,移除该字段后流无法播放:
> cat video.sdp v=0 o=- 0 0 IN IP4 127.0.0.1 s=No Name c=IN IP4 127.0.0.1 t=0 0 a=tool:libavformat 58.76.100 m=video 5004 RTP/AVP 96 b=AS:1034 a=rtpmap:96 H264/90000 a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QANKzZQDAA7fiMBagICAoAAAMAAgAAAwDwHjBjLA==,aOvjyyLA; profile-level-id=640034
原自行生成的SDP内容
无sprop-parameter-sets字段:
> cat test.sdp v=0 o=- 0 0 IN IP4 127.0.0.1 s=No Name c=IN IP4 127.0.0.1 t=0 0 a=tool:libavformat 58.76.100 m=video 44499 RTP/AVP 96 b=AS:2000 a=rtpmap:96 H264/90000 a=fmtp:96 packetization-mode=1
核心代码(修改前)
// variables std::vector<std::uint8_t> imgbuf; AVFormatContext *ofmt_ctx = nullptr; AVCodec *out_codec = nullptr; AVStream *out_stream = nullptr; AVCodecContext *out_codec_ctx = nullptr; SwsContext *swsctx = nullptr; cv::Mat canvas_; unsigned int height_; unsigned int width_; unsigned int fps_; AVFrame *frame_ = nullptr; AVOutputFormat *format = av_guess_format("rtp", nullptr, nullptr); const auto url = std::string("rtp://127.0.0.1:5001"); avformat_alloc_output_context2(ofmt_ctx, format, format->name, url.c_str()); out_codec = avcodec_find_encoder(AV_CODEC_ID_H264); stream = avformat_new_stream(ofmt_ctx, out_codec); out_codec_ctx = avcodec_alloc_context3(out_codec); // then, for each incoming image: while (receive_image) { static bool first_time = true; if (first_time) { // discover necessary params such as image dimensions from the first // received image first_time = false; height_ = image.rows; width_ = image.cols; codec_ctx->codec_tag = 0; codec_ctx->bit_rate = 2e6; // does nothing, unfortunately codec_ctx->thread_count = 1; codec_ctx->codec_id = AV_CODEC_ID_H264; codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO; codec_ctx->width = width_; codec_ctx->height = height_; codec_ctx->gop_size = 6; codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; codec_ctx->framerate = fps_; codec_ctx->time_base = av_inv_q(fps_); avcodec_parameters_from_context(stream, out_codec_ctx); // this stuff is empty: is that the problem? stream->codecpar->extradata = codec_ctx->extradata; stream->codecpar->extradata_size = codec_ctx->extradata_size; AVDictionary *codec_options = nullptr; av_dict_set(&codec_options, "profile", "high", 0); av_dict_set(&codec_options, "preset", "ultrafast", 0); av_dict_set(&codec_options, "tune", "zerolatency", 0); // open video encoder avcodec_open2(codec_ctx, codec, &codec_options); stream->time_base.num = 1; stream->time_base.den = fps_; avio_open(&(ofmt_ctx->pb), ofmt_ctx->filename, AVIO_FLAG_WRITE); /* Write a file for VLC */ char buf[200000]; AVFormatContext *ac[] = {ofmt_ctx}; av_sdp_create(ac, 1, buf, 20000); printf("sdp:\n%s\n", buf); FILE *fsdp = fopen("test.sdp", "w"); fprintf(fsdp, "%s", buf); fclose(fsdp); swsctx = sws_getContext(width_, height_, AV_PIX_FMT_BGR24, width_, height_, out_codec_ctx->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr); } if (!frame_) { frame_ = av_frame_alloc(); std::uint8_t *framebuf = new uint8_t[av_image_get_buffer_size( codec_ctx->pix_fmt, width_, height_, 1)]; av_image_fill_arrays(frame_->data, frame_->linesize, framebuf, codec_ctx->pix_fmt, width, height, 1); frame_->width = width_; frame_->height = height_; frame_->format = static_cast<int>(codec_ctx->pix_fmt); success = avformat_write_header(ofmt_ctx, nullptr); } if (imgbuf.empty()) { imgbuf.resize(height_ * width_ * 3 + 16); canvas_ = cv::Mat(height_, width_, CV_8UC3, imgbuf.data(), width_ * 3); } else { image.copyTo(canvas_); } const int stride[] = {static_cast<int>(image.step[0])}; sws_scale(swsctx, &canvas_.data, stride, 0, canvas_.rows, frame_->data, frame_->linesize); frame_->pts += av_rescale_q(1, out_codec_ctx->time_base, stream->time_base); AVPacket pkt = {0}; avcodec_send_frame(out_codec_ctx, frame_); avcodec_receive_packet(out_codec_ctx, &pkt); av_interleaved_write_frame(ofmt_ctx, &pkt); }
问题更新
已添加代码this->out_codec_ctx->flags |=AV_CODEC_FLAG_GLOBAL_HEADER;,并将avcodec_parameters_from_context()调用移到avcodec_open2()之后(编码器打开前extradata为空),目前SDP中已生成sprop-parameter-sets字段,但VLC仍然无法播放该流。
解决方案
按照以下步骤逐一排查修改即可解决:
- 调整SDP生成顺序
av_sdp_create必须在avformat_write_header调用之后执行,因为写入头文件的过程中会补全流的元信息,提前生成SDP会缺少必要参数。 - 强制SPS/PPS随IDR帧发送
添加编码器参数,强制每个IDR帧前都携带SPS和PPS,避免VLC等严格播放器因为收不到参数集而解码失败:
av_dict_set(&codec_options, "x264-params", "sps-idr=1", 0);
- 补充SDP的profile标识
在SDP的fmtp行中明确添加profile-level-id,值可从编码器extradata中解析,也可以直接和x264配置的profile对应,比如high profile level 3.4对应值为640022,避免VLC解析profile不匹配。 - 时间戳校准
确保AVPacket的pts/dts是连续递增的,没有跳变或回退,VLC对时间戳的容错性远低于ffplay,时间戳异常会直接判定为无有效数据。 - 端口一致性校验
确认SDP里的IP、端口和实际发送的RTP流端口完全一致,同时关闭接收端防火墙,避免端口被拦截导致VLC收不到数据。
内容的提问来源于stack exchange,提问作者oarfish
相关产品推荐
相关产品推荐

