关于ffmpeg解码后重编码问题

Wong Chinhang 2019-05-30 08:20:06
项目需要,用ffmpeg做了一个rtmp拉h264流然后解码成avframe处理后再重编码成h264流推到另一个流媒体服务器上,但是这个流播放时出现以下的解码问题,提示nal头过大还有一些数据问题。请问各位这是编码时候哪个步骤错了?以下附上问题截图以及项目代码。


static double r2d(AVRational r)
{
return r.num == 0 || r.den == 0 ? 0. : (double)r.num / (double)r.den;
}

int main(int argc, char *argv[]) {
int videoIndex = -1;

// 初始化编解码
avcodec_register_all();

// 所有代码执行之前要调用av_register_all和avformat_network_init
// 初始化所有的封装和解封装 flv mp4 mp3 mov。不包含编码和解码
av_register_all();

// 初始化网络库
avformat_network_init();

// 使用的相对路径,执行文件在bin目录下。test.mp4放到bin目录下即可
const char *inUrl = "rtmp://live.hkstv.hk.lxdns.com/live/hks2";

// 输出的地址
const char *outUrl = "rtmp://127.0.0.1/live/stream";

//////////////////////////////////////////////////////////////////
// 输入流处理部分
/////////////////////////////////////////////////////////////////
AVFormatContext *ictx = NULL;

//AVOutputFormat *ofmt = NULL;

// 打开文件,解封文件头
int ret = avformat_open_input(&ictx, inUrl, 0, NULL);
if (ret < 0) {
return avError(ret);
}
cout << "avformat_open_input success!" << endl;

// 获取音频视频的信息 .h264 flv 没有头信息
ret = avformat_find_stream_info(ictx, 0);
if (ret != 0) {
return avError(ret);
}
// 打印视频视频信息
// 0打印所有 inUrl 打印时候显示,
av_dump_format(ictx, 0, inUrl, 0);

//////////////////////////////////////////////////////////////////
// 输出流处理部分
/////////////////////////////////////////////////////////////////
AVFormatContext * octx = NULL;
// 如果是输入文件 flv可以不传,可以从文件中判断。如果是流则必须传
// 创建输出上下文
ret = avformat_alloc_output_context2(&octx, NULL, "flv", outUrl);
if (ret < 0) {
return avError(ret);
}
cout << "avformat_alloc_output_context2 success!" << endl;

//ofmt = octx->oformat;
cout << "nb_streams " << ictx->nb_streams << endl;
int i;

for (i = 0; i < 1
/*ictx->nb_streams*/; i++) {
// 获取输入视频流
AVStream *in_stream = ictx->streams[i];
// 为输出上下文添加音视频流(初始化一个音视频流容器)
AVStream *out_stream = avformat_new_stream(octx, in_stream->codec->codec);
if (!out_stream) {
printf("未能成功添加音视频流\n");
ret = AVERROR_UNKNOWN;
}
// 将输入编解码器上下文信息 copy 给输出编解码器上下文
ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
if (ret < 0) {
printf("copy 编解码器上下文失败\n");
}
out_stream->codecpar->codec_tag = 0;

out_stream->codec->codec_tag = 0;
if (octx->oformat->flags & AVFMT_GLOBALHEADER) {
out_stream->codec->flags = out_stream->codec->flags | AV_CODEC_FLAG_GLOBAL_HEADER;
}
}

// 输入流数据的数量循环
for (i = 0; i < ictx->nb_streams; i++) {
if (ictx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoIndex = i;
break;
}
}

av_dump_format(octx, 0, outUrl, 1);

//////////////////////////////////////////////////////////////////
// 获取编解码信息
// TODO: 1、获取解码信息,打开解码上下文;2、获取编码信息,打开编码上下文;3、后续需要使用avcodec_free_context释放编码上下文对象;
/////////////////////////////////////////////////////////////////
// 获取视频流
AVStream *stream = ictx->streams[videoIndex];
// 获取其编码格式
AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id);
// 获取解码上下文
AVCodecContext *codec_ctx_decoder = avcodec_alloc_context3(codec);
// codecpar包含了大部分解码器相关的信息,直接从AVCodecParameters复制到AVCodecContext
avcodec_parameters_to_context(codec_ctx_decoder, stream->codecpar);
// 获取视频流的帧率
av_codec_set_pkt_timebase(codec_ctx_decoder, stream->time_base);
// 打开视频解码上下文
if(avcodec_open2(codec_ctx_decoder, codec, nullptr)){
av_log(NULL, AV_LOG_ERROR, "打开解码器失败\n");
}

// 获取其编码格式
codec = avcodec_find_encoder(stream->codecpar->codec_id);
// 视频帧率
int fps = stream->avg_frame_rate.num / stream->avg_frame_rate.den;
// 获取编码上下文
AVCodecContext *codec_ctx_encoder = avcodec_alloc_context3(codec);
// codecpar包含了大部分解码器相关的信息,直接从AVCodecParameters复制到AVCodecContext
avcodec_parameters_to_context(codec_ctx_encoder, stream->codecpar);
// 获取视频流的帧率
// codec_ctx_encoder->time_base = stream->time_base;
codec_ctx_encoder->time_base = { 1, fps };
// 设置源数据格式
// codec_ctx_encoder->pix_fmt = AV_PIX_FMT_BGR24;
// 打开视频编码上下文
if(avcodec_open2(codec_ctx_encoder, codec, nullptr)){
av_log(NULL, AV_LOG_ERROR, "打开编码器失败\n");
}
//////////////////////////////////////////////////////////////////
// 自定义编码器
/////////////////////////////////////////////////////////////////
// a 找到编码器
AVCodec *codecSelf = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codecSelf)
{
cout << "获取编码器失败" << endl;
}
// b 创建编码器上下文
// 编码器上下文
AVCodecContext *vc_self = NULL;
vc_self = avcodec_alloc_context3(codecSelf);
if (!vc_self)
{
// throw exception("avcodec_alloc_context3 failed!");
}
//c 配置编码器参数
vc_self->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //全局参数
vc_self->codec_id = codecSelf->id;
vc_self->thread_count = 8;

vc_self->bit_rate = 50 * 1024 * 8;//压缩后每秒视频的bit位大小 50kB
vc_self->width = codec_ctx_decoder->width;
vc_self->height = codec_ctx_decoder->height;
vc_self->time_base = { 1,fps };
vc_self->framerate = { fps,1 };
cout << "fps = " << fps << endl;
//画面组的大小,多少帧一个关键帧
vc_self->gop_size = 500;
vc_self->max_b_frames = 3;
vc_self->pix_fmt = AV_PIX_FMT_YUV420P;
//d 打开编码器上下文
if(avcodec_open2(vc_self, codecSelf, nullptr)){
av_log(NULL, AV_LOG_ERROR, "打开编码器失败\n");
}

//////////////////////////////////////////////////////////////////
// 准备推流
/////////////////////////////////////////////////////////////////

// 打开IO
ret = avio_open(&octx->pb, outUrl, AVIO_FLAG_WRITE);
if (ret < 0) {
avError(ret);
}

// 写入头部信息
ret = avformat_write_header(octx, 0);
if (ret < 0) {
avError(ret);
}
cout << "avformat_write_header Success!" << endl;
// 推流每一帧数据
// int64_t pts [ pts*(num/den) 第几秒显示]
// int64_t dts 解码时间 [P帧(相对于上一帧的变化) I帧(关键帧,完整的数据) B帧(上一帧和下一帧的变化)] 有了B帧压缩率更高。
// uint8_t *data
// int size
// int stream_index
// int flag
AVPacket pkt;
// 获取当前的时间戳 微妙
long long start_time = av_gettime();
long long frame_index = 1;
// 上一帧的dts记录
int64_t last_dts = -1;

while (1) {
// 输入输出视频流
AVStream *in_stream, *out_stream;
// 获取解码前数据
ret = av_read_frame(ictx, &pkt);
if (ret < 0) {
break;
}

if(pkt.stream_index == videoIndex){
AVFrame* pAvFrame;

pAvFrame = av_frame_alloc();
{
auto iRet = avcodec_send_packet(codec_ctx_decoder, &pkt);
if(iRet == 0){
iRet = avcodec_receive_frame(codec_ctx_decoder, pAvFrame);
}

if (iRet == AVERROR_EOF) {
av_log(NULL, AV_LOG_ERROR, "the decoder has been flushed \n");
} else if (iRet == AVERROR(EAGAIN)) {
av_log(NULL, AV_LOG_ERROR, "decode:input is not accepted right now \n");
continue;
} else if (iRet == AVERROR(EINVAL)) {
av_log(NULL, AV_LOG_ERROR, "codec not opened \n");
}

if (iRet != 0) {
// 没有图片
cout << "视频解码,未获取到图片:" << endl;
}

if(iRet == 0){
av_packet_unref(&pkt);
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;

pAvFrame->pts = frame_index;

++frame_index;

// 编码
ret = avcodec_send_frame(vc_self, pAvFrame);
cout << ret << endl;
if (ret == 0) {
// 接受编码后的数据
ret = avcodec_receive_packet(vc_self, &pkt);
cout << ret << endl;
if (ret == AVERROR_EOF) {
av_log(NULL, AV_LOG_ERROR, "the encoder has been flushed \n");
} else if (ret == AVERROR(EAGAIN)) {
av_log(NULL, AV_LOG_ERROR, "encode:input is not accepted right now \n");
continue;
} else if (ret == AVERROR(EINVAL)) {
av_log(NULL, AV_LOG_ERROR, "codec not opened \n");
}
} else {
{
av_log(NULL, AV_LOG_ERROR, "avcodec_send_frame error\n");
}
}
av_frame_free(&pAvFrame);
}
in_stream = ictx->streams[pkt.stream_index];
out_stream = octx->streams[pkt.stream_index];
cout << "duration = " << pkt.duration << endl;
int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(ictx->streams[videoIndex]->r_frame_rate);
pkt.duration = (double)calc_duration / (double)(av_q2d(ictx->streams[videoIndex]->time_base)*AV_TIME_BASE);
// 计算延时后,重新指定时间戳
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 = (int)av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);

ret = av_interleaved_write_frame(octx, &pkt);

if (ret < 0) {
printf("发送数据包出错\n");
break;
}

// 释放
av_packet_unref(&pkt);
}
...全文
344 3 打赏 收藏 转发到动态 举报
写回复
用AI写文章
3 条回复
切换为时间正序
请发表友善的回复…
发表回复
Wong Chinhang 2019-05-31
  • 打赏
  • 举报
回复
引用 1 楼 Wong Chinhang 的回复:
以上为解码推出的流发生的错误截图。
smwhotjay 2019-05-30
  • 打赏
  • 举报
回复
我利用ffmpeg 转发rtmp publish到另一个rtmp直播。用的是命令形式。效果能用,当直播一方断开则ffmpeg进程退出 ffmpeg -rtmp_live 1 -i rtmp://127.0.0.1/live/a -c copy -f flv rtmp://127.0.0.1:1936/hls/a ;
Wong Chinhang 2019-05-30
  • 打赏
  • 举报
回复
以上为解码推出的流发生的错误截图。

2,543

社区成员

发帖
与我相关
我的任务
社区描述
专题开发/技术/项目 多媒体/流媒体开发
社区管理员
  • 多媒体/流媒体开发社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧