我正在使用 FFmpeg 的 C API 将视频流
rtmp://....
推送到 SRS 服务器。juren-30s.mp4
的 MP4 文件。 juren-30s-5.mp4
的 MP4文件。
我的代码(请参阅下文)在以下步骤中使用时工作正常:
mp4 -> demux -> decode -> rgb images -> encode -> mux -> mp4
。
问题:
当我将输出流更改为名为
rtmp://ip:port/live/stream_nb_23
的在线 RTMP url 时(只是一个示例,您可以根据您的服务器和规则进行更改。)
结果:此代码将被损坏
mp4 -> rtmp(flv)
。
我尝试过的:
更改输出格式
当我初始化 avformat_alloc_output_context2
时,我将输出格式参数更改为
flv。但这并没有帮助。
调试输出
当我执行
ffprobe rtmp://ip:port/live/xxxxxxx
时,出现以下错误,不知道为什么:
[h264 @ 0x55a925e3ba80] luma_log2_weight_denom 12 is out of range
[h264 @ 0x55a925e3ba80] Missing reference picture, default is 2
[h264 @ 0x55a925e3ba80] concealing 8003 DC, 8003 AC, 8003 MV errors in P frame
[h264 @ 0x55a925e3ba80] QP 4294966938 out of range
[h264 @ 0x55a925e3ba80] decode_slice_header error
[h264 @ 0x55a925e3ba80] no frame!
[h264 @ 0x55a925e3ba80] luma_log2_weight_denom 21 is out of range
[h264 @ 0x55a925e3ba80] luma_log2_weight_denom 10 is out of range
[h264 @ 0x55a925e3ba80] chroma_log2_weight_denom 12 is out of range
[h264 @ 0x55a925e3ba80] Missing reference picture, default is 0
[h264 @ 0x55a925e3ba80] decode_slice_header error
[h264 @ 0x55a925e3ba80] QP 4294967066 out of range
[h264 @ 0x55a925e3ba80] decode_slice_header error
[h264 @ 0x55a925e3ba80] no frame!
[h264 @ 0x55a925e3ba80] QP 341 out of range
[h264 @ 0x55a925e3ba80] decode_slice_header error
我对如何使用 FFmpeg C-API 生成正确的输出流格式的 MP4 和 RTMP 之间的区别感到困惑。
此外,我还想学习如何使用FFmpeg C-api将视频和音频流转换为其他格式,例如
flv
、ts
、rtsp
等
重现问题的代码:
下面所示的 C 代码可以在 Main.c 中找到,这是要重现的最小版本。可以重现并运行成功。
RTMP而不出现视频无法播放的问题?
#include <stdio.h>
#include "libavformat/avformat.h"
int main()
{
int ret = 0; int err;
//Open input file
char filename[] = "juren-30s.mp4";
AVFormatContext *fmt_ctx = avformat_alloc_context();
if (!fmt_ctx) {
printf("error code %d \n",AVERROR(ENOMEM));
return ENOMEM;
}
if((err = avformat_open_input(&fmt_ctx, filename,NULL,NULL)) < 0){
printf("can not open file %d \n",err);
return err;
}
//Open the decoder
AVCodecContext *avctx = avcodec_alloc_context3(NULL);
ret = avcodec_parameters_to_context(avctx, fmt_ctx->streams[0]->codecpar);
if (ret < 0){
printf("error code %d \n",ret);
return ret;
}
AVCodec *codec = avcodec_find_decoder(avctx->codec_id);
if ((ret = avcodec_open2(avctx, codec, NULL)) < 0) {
printf("open codec faile %d \n",ret);
return ret;
}
//Open the output file container
char filename_out[] = "juren-30s-5.mp4";
AVFormatContext *fmt_ctx_out = NULL;
err = avformat_alloc_output_context2(&fmt_ctx_out, NULL, NULL, filename_out);
if (!fmt_ctx_out) {
printf("error code %d \n",AVERROR(ENOMEM));
return ENOMEM;
}
//Add all the way to the container context
AVStream *st = avformat_new_stream(fmt_ctx_out, NULL);
st->time_base = fmt_ctx->streams[0]->time_base;
AVCodecContext *enc_ctx = NULL;
AVPacket *pt = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
AVPacket *pkt_out = av_packet_alloc();
int frame_num = 0; int read_end = 0;
for(;;){
if( 1 == read_end ){ break;}
ret = av_read_frame(fmt_ctx, pkt);
//Skip and do not process audio packets
if( 1 == pkt->stream_index ){
av_packet_unref(pt);
continue;
}
if ( AVERROR_EOF == ret) {
//After reading the file, the data and size of pkt should be null at this time
avcodec_send_packet(avctx, NULL);
}else {
if( 0 != ret){
printf("read error code %d \n",ret);
return ENOMEM;
}else{
retry:
if (avcodec_send_packet(avctx, pkt) == AVERROR(EAGAIN)) {
printf("Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
//Here you can consider sleeping for 0.1 seconds and returning EAGAIN. This is usually because there is a bug in ffmpeg's internal API.
goto retry;
}
//Release the encoded data in pkt
av_packet_unref(pt);
}
}
//The loop keeps reading data from the decoder until there is no more data to read.
for(;;){
//Read AVFrame
ret = avcodec_receive_frame(avctx, frame);
/* Release the YUV data in the frame,
* Since av_frame_unref is called in the avcodec_receive_frame function, the following code can be commented.
* So we don't need to manually unref this AVFrame
* */
//off_frame_unref(frame);
if( AVERROR(EAGAIN) == ret ){
//Prompt EAGAIN means the decoder needs more AVPackets
//Jump out of the first layer of for and let the decoder get more AVPackets
break;
}else if( AVERROR_EOF == ret ){
/* The prompt AVERROR_EOF means that an AVPacket with both data and size NULL has been sent to the decoder before.
* Sending NULL AVPacket prompts the decoder to flush out all cached frames.
* Usually a NULL AVPacket is sent only after reading the input file, or when another video stream needs to be decoded with an existing decoder.
*
* */
/* Send null AVFrame to the encoder and let the encoder flush out the remaining data.
* */
ret = avcodec_send_frame(enc_ctx, NULL);
for(;;){
ret = avcodec_receive_packet(enc_ctx, pkt_out);
//It is impossible to return EAGAIN here, if there is any, exit directly.
if (ret == AVERROR(EAGAIN)){
printf("avcodec_receive_packet error code %d \n",ret);
return ret;
}
if ( AVERROR_EOF == ret ){ break; }
//Encode the AVPacket, print some information first, and then write it to the file.
printf("pkt_out size : %d \n",pkt_out->size);
//Set the stream_index of AVPacket so that you know which stream it is.
pkt_out->stream_index = st->index;
//Convert the time base of AVPacket to the time base of the output stream.
pkt_out->pts = av_rescale_q_rnd(pkt_out->pts, fmt_ctx->streams[0]->time_base, st->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt_out->dts = av_rescale_q_rnd(pkt_out->dts, fmt_ctx->streams[0]->time_base, st->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt_out->duration = av_rescale_q_rnd(pkt_out->duration, fmt_ctx->streams[0]->time_base, st->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
ret = av_interleaved_write_frame(fmt_ctx_out, pkt_out);
if (ret < 0) {
printf("av_interleaved_write_frame faile %d \n",ret);
return ret;
}
av_packet_unref(pt_out);
}
av_write_trailer(fmt_ctx_out);
//Jump out of the second layer of for, the file has been decoded.
read_end = 1;
break;
}else if( ret >= 0 ){
//Only when a frame is decoded can the encoder be initialized.
if( NULL == enc_ctx ){
//Open the encoder and set encoding information.
AVCodec *encode = avcodec_find_encoder(AV_CODEC_ID_H264);
enc_ctx = avcodec_alloc_context3(encode);
enc_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
enc_ctx->bit_rate = 400000;
enc_ctx->framerate = avctx->framerate;
enc_ctx->gop_size = 30;
enc_ctx->max_b_frames = 10;
enc_ctx->profile = FF_PROFILE_H264_MAIN;
/*
* In fact, the following information is also available in the container. You can also open the encoder directly in the container at the beginning.
* I took these encoder parameters from AVFrame because the difference in the container is final.
* Because the AVFrame you decoded may go through a filter, the information will be transformed after the filter, but this article does not use filters.
*/
//The time base of the encoder should be the time base of AVFrame, because AVFrame is the input. The time base of AVFrame is the time base of the stream.
enc_ctx->time_base = fmt_ctx->streams[0]->time_base;
enc_ctx->width = fmt_ctx->streams[0]->codecpar->width;
enc_ctx->height = fmt_ctx->streams[0]->codecpar->height;
enc_ctx->sample_aspect_ratio = st->sample_aspect_ratio = frame->sample_aspect_ratio;
enc_ctx->pix_fmt = frame->format;
enc_ctx->color_range = frame->color_range;
enc_ctx->color_primaries = frame->color_primaries;
enc_ctx->color_trc = frame->color_trc;
enc_ctx->colorspace = frame->colorspace;
enc_ctx->chroma_sample_location = frame->chroma_location;
/* Note that the value of this field_order is different for different videos. I have written it here.
* Because the video in this article is AV_FIELD_PROGRESSIVE
* The production environment needs to process different videos
*/
enc_ctx->field_order = AV_FIELD_PROGRESSIVE;
/* Now we need to copy the encoder parameters to the stream. When decoding, assign parameters from the stream to the decoder.
* Now let’s do it in reverse.
* */
ret = avcodec_parameters_from_context(st->codecpar,enc_ctx);
if (ret < 0){
printf("error code %d \n",ret);
return ret;
}
if ((ret = avcodec_open2(enc_ctx, encode, NULL)) < 0) {
printf("open codec faile %d \n",ret);
return ret;
}
//Formally open the output file
if ((ret = avio_open2(&fmt_ctx_out->pb, filename_out, AVIO_FLAG_WRITE,&fmt_ctx_out->interrupt_callback,NULL)) < 0) {
printf("avio_open2 fail %d \n",ret);
return ret;
}
//Write the file header first.
ret = avformat_write_header(fmt_ctx_out,NULL);
if (ret < 0) {
printf("avformat_write_header fail %d \n",ret);
return ret;
}
}
//Send AVFrame to the encoder, and then continuously read AVPacket
ret = avcodec_send_frame(enc_ctx, frame);
if (ret < 0) {
printf("avcodec_send_frame fail %d \n",ret);
return ret;
}
for(;;){
ret = avcodec_receive_packet(enc_ctx, pkt_out);
if (ret == AVERROR(EAGAIN)){ break; }
if (ret < 0){
printf("avcodec_receive_packet fail %d \n",ret);
return ret;
}
//Encode the AVPacket, print some information first, and then write it to the file.
printf("pkt_out size : %d \n",pkt_out->size);
//Set the stream_index of AVPacket so that you know which stream it is.
pkt_out->stream_index = st->index;
//Convert the time base of AVPacket to the time base of the output stream.
pkt_out->pts = av_rescale_q_rnd(pkt_out->pts, fmt_ctx->streams[0]->time_base, st->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt_out->dts = av_rescale_q_rnd(pkt_out->dts, fmt_ctx->streams[0]->time_base, st->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt_out->duration = av_rescale_q_rnd(pkt_out->duration, fmt_ctx->streams[0]->time_base, st->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
ret = av_interleaved_write_frame(fmt_ctx_out, pkt_out);
if (ret < 0) {
printf("av_interleaved_write_frame faile %d \n",ret);
return ret;
}
av_packet_unref(pt_out);
}
}
else{ printf("other fail \n"); return ret;}
}
}
av_frame_free(&frame); av_packet_free(&pt); av_packet_free(&pkt_out);
//Close the encoder and decoder.
avcodec_close(avctx); avcodec_close(enc_ctx);
//Release container memory.
avformat_free_context(fmt_ctx);
//Must adjust avio_closep, otherwise the data may not be written in, it will be 0kb
avio_closep(&fmt_ctx_out->pb);
avformat_free_context(fmt_ctx_out);
printf("done \n");
return 0;
}
这个问题已经困扰我大约三个星期了。我仍然不知道关键的错误存在于哪里。如果任何 FFmpeg 专家可以帮助我,我真的很感激。
时间码重定标器也以不同的方式调用:
/* copy packet */
//Convert PTS/DTS
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
正确处理 H.264 流。
我想,你还应该使用函数
复制整个AVFormatContext
avcodec_copy_context(out_stream->codec, in_stream->codec);
而不是复制参数来获取正确的标头。