赞
踩
最近闲这没事,整理了一下之前开发过的音视频编解码库,主要基于ffmpeg,实现音视频的编解码、视频流添加文字,音视频同步到MP4等功能。有需要的小伙伴可以参考参考,如果写的有什么不对的地方,欢迎大家指正。
大概画下哈,明白意思即可,请自觉忽略画的水平。
软解码、硬解码
软解码:使用CPU解码
硬解码:使用GPU解码, 实现了 QSV 、NVENC 编解码。(如果使用硬解码,无论是qsv ,还是nvenc ,都需要我们重新编译ffmpeg,来支持 qsv 或 nvenc 编解码)
代码
- h264 : get_decoder_context(AV_CODEC_ID_H264)
-
- nvenc: avcodec_find_decoder_by_name("h264_cuvid");
-
- qsv : avcodec_find_decoder_by_name("h264_qsv")
访问 rtsp ,获取 rtsp 信息流
- int Qffmpeg::find_stream_info(char *rtsp, AVFormatContext **ifmt_ctx)
- {
- if ((m_ret = avformat_open_input(ifmt_ctx, rtsp, 0, &p_optionsDict)) < 0)
- {
- printf("Could not open input file:%s\n", rtsp);
- return -1;
- }
-
- if ((m_ret = avformat_find_stream_info(*ifmt_ctx, NULL)) < 0)
- { // Get information on the input file (number of streams etc.).
- printf("Could not open find stream info:%s\n", rtsp);
- return -1;
- }
-
- for (unsigned int i = 0; i < (*ifmt_ctx)->nb_streams; i++)
- { // dump information
- av_dump_format(*ifmt_ctx, i, rtsp, 0);
- }
-
- return 1;
- }
开启视频解码器
- AVCodecContext *Qffmpeg::get_decoder_context(AVStream *p_vst)
- {
- #if QVS
- AVCodec *videoCodec = avcodec_find_decoder_by_name("h264_qsv");
- #elif CUDA
- AVCodec *videoCodec = avcodec_find_decoder_by_name("h264_cuvid");
- #else
- AVCodec *videoCodec = avcodec_find_decoder(p_vst->codecpar->codec_id);
- #endif
- AVCodecContext *videoCodecCtx = avcodec_alloc_context3(videoCodec);
-
- avcodec_parameters_to_context(videoCodecCtx, p_vst->codecpar);
-
- if (videoCodec == NULL)
- {
- printf("encode avcodec_parameters_to_context faild.\n");
- avcodec_free_context(&videoCodecCtx);
- return nullptr;
- }
- if (avcodec_open2(videoCodecCtx, videoCodec, NULL) < 0)
- {
- printf("decode avcodec_open2 faild.\n");
- avcodec_free_context(&videoCodecCtx);
- return nullptr;
- }
- return videoCodecCtx;
- }
开启视频编码器(可自行设定参数)
- AVCodecContext *Qffmpeg::get_encoder_context(AVStream *p_vst, AVCodecID codecId, AVPixelFormat pix_fmt, int fps, int width, int height)
- {
-
- #if QVS
- AVCodec *pCodec264 = avcodec_find_encoder_by_name("h264_qsv");
- #elif CUDA
- AVCodec *pCodec264 = avcodec_find_encoder_by_name("h264_nvenc");
- #else
- AVCodec *pCodec264 = avcodec_find_encoder(codecId);
- #endif
-
- if (NULL == pCodec264)
- {
- printf("avcodec_find_encoder fail. \n");
- return NULL;
- }
-
- AVCodecContext *c = avcodec_alloc_context3(pCodec264);
-
- c->codec_id = p_vst->codecpar->codec_id;
- c->codec_type = p_vst->codecpar->codec_type;
- c->width = p_vst->codecpar->width;
- c->height = p_vst->codecpar->height;
- #if QVS
- c->pix_fmt = AV_PIX_FMT_NV12;
- #else
- c->pix_fmt = pix_fmt;
- #endif
- int frame_rate = p_vst->avg_frame_rate.num / p_vst->avg_frame_rate.den;
- c->time_base = { 1, frame_rate * 2 };
- c->flags = 0;
-
- // ========================= CBR ==========================
- c->bit_rate = 4000 * 1000;
- c->bit_rate_tolerance = 4000 * 1000;
- c->gop_size = 10;
- c->max_b_frames = 0;
- c->qmin = 10;
- c->qmax = 50;
- c->qblur = 0.0;
- c->spatial_cplx_masking = 0.3;
- c->me_pre_cmp = 2;
- c->b_quant_factor = 1.25;
- c->b_quant_offset = 1.25;
- c->i_quant_factor = 0.8;
- c->i_quant_offset = 0.0;
- c->dct_algo = 0;
- c->lumi_masking = 0.0;
- c->dark_masking = 0.0;
- // ========================= CBR ==========================
-
- #if QVS
- av_opt_set(c->priv_data, "tune", "zerolatency", 0);
- #elif CUDA
- av_opt_set(c->priv_data, "tune", "zerolatency", 0);
- #else
- // 修改编码形式 ultrafast、superfast、veryfast、faster、fast、medium、slow、slower、veryslow、placebo
- av_opt_set(c->priv_data, "preset", "ultrafast", 0);
- // film 电影、真人类型 animation 动画 grain 需要保留大量的grain时用 stillimage 静态图像编码时使用
- // psnr 为提高psnr做了优化的参 ssim 为提高ssim做了优化的参数
- // fastdecode 可以快速解码的参 zerolatency 零延迟,用在需要非常低的延迟的情况下,比如电视电话会议的编码
- av_opt_set(c->priv_data, "tune", "zerolatency", 0);
- #endif
-
- int ret = avcodec_open2(c, pCodec264, NULL);
- if (ret < 0)
- {
- printf("encode avcodec_open2 faild.[%d] \n", ret);
- avcodec_free_context(&c);
- return NULL;
- }
- return c;
- }
解码视频流
- int ret = avcodec_send_packet(p_videoDeCodecCtx, &m_pkt);
- av_packet_unref(&m_pkt);
- if (ret != 0)
- {
- continue;
- }
-
- AVFrame *frame_in = av_frame_alloc();
- ret = avcodec_receive_frame(p_videoDeCodecCtx, frame_in);
- if (ret != 0)
- {
- printf("avcodec_receive_frame failed.[%d] \n", m_ret);
- av_frame_free(&frame_in);
- continue;
- }
解码成功后,添加文字
- AVFrame *Qffmpeg::add_osd_info(AVFrame *frame_in, OSD_INFO *info)
- {
-
- // todo
- if (info == NULL)
- {
- //printf("=============== NO OSD INFO ===============");
- return frame_in;
- }
-
- AVFilterContext *buffersink_ctx;
- AVFilterContext *buffersrc_ctx;
-
- // 添加osd
- AVFilterGraph *filter_graph = add_filter(frame_in, &buffersink_ctx, &buffersrc_ctx, p_videoDeCodecCtx, info);
-
- if (filter_graph == nullptr)
- {
- printf("=================== add_filter failed ===================\n");
- av_frame_free(&frame_in);
- return nullptr;
- }
-
- // 添加水印
- m_ret = av_buffersrc_add_frame(buffersrc_ctx, frame_in);
- if (m_ret < 0)
- {
- printf("=================== av_buffersrc_add_frame failed.[%d] ===================\n", m_ret);
- av_frame_free(&frame_in);
- avfilter_graph_free(&filter_graph);
- filter_graph = NULL;
- return nullptr;
- }
- AVFrame *frame_out = av_frame_alloc();
- // 解码水印
- m_ret = av_buffersink_get_frame(buffersink_ctx, frame_out);
-
- if (m_ret < 0)
- {
- printf("=================== av_buffersink_get_frame failed.[%d]===================\n", m_ret);
- av_frame_free(&frame_in);
- av_frame_free(&frame_out);
- avfilter_graph_free(&filter_graph);
- filter_graph = NULL;
- return nullptr;
- }
- av_frame_free(&frame_in);
- avfilter_graph_free(&filter_graph);
- filter_graph = NULL;
-
- return frame_out;
- }
添加水印后,实现转换rgb 和 编码视频流。
图片转换成rgb格式(硬解码: nv12 -> rgb , 软解码: yuv-> rgb) 。转换rgb格式,主要是项目中需要,如果不需要,可pass。
- uint8_t *Qffmpeg::convert_rgb(AVFrame *frame, int srcW, int srcH, AVPixelFormat srcPixFmt, int desW, int desH, AVPixelFormat desPixFmt, int &nSize)
- {
- // old
- //int bytes = avpicture_get_size(desPixFmt, desW, desH);
- int bytes = av_image_get_buffer_size(desPixFmt, desW, desH, 1);
- uint8_t *buffer_rgb = (uint8_t *)av_malloc(bytes);
- AVFrame *pFrameRGB = av_frame_alloc();
- // old
- //avpicture_fill((AVPicture *)pFrameRGB, buffer_rgb, desPixFmt, desW, desH);
- av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer_rgb, desPixFmt, desW, desH, 1);
- SwsContext *img_convert_ctx = sws_getContext(srcW, srcH, srcPixFmt, desW, desH, desPixFmt, NULL, NULL, NULL, NULL);
-
- if (img_convert_ctx == NULL)
- {
- printf("can't init convert context.\n");
- av_frame_free(&pFrameRGB);
- av_free(buffer_rgb);
- return nullptr;
- }
-
- sws_scale(img_convert_ctx, frame->data, frame->linesize, 0, srcH, pFrameRGB->data, pFrameRGB->linesize);
-
- sws_freeContext(img_convert_ctx);
-
- av_frame_free(&pFrameRGB);
-
- nSize = bytes;
-
- return buffer_rgb;
- }
编码视频流
- int Qffmpeg::encode_and_call_back(AVFrame *frame, int w, int h)
- {
- if (p_videoEnCodecCtx == NULL)
- return -1;
-
- int ret = avcodec_send_frame(p_videoEnCodecCtx, frame);
- if (ret != 0)
- {
- return -1;
- }
-
- AVPacket pkt;
- av_init_packet(&pkt);
- pkt.data = NULL;
- pkt.size = 0;
-
- ret = avcodec_receive_packet(p_videoEnCodecCtx, &pkt);
- if (ret != 0)
- {
- printf("=================== avcodec_receive_packet failed.[%d] ===================\n", ret);
- av_packet_unref(&pkt);
- return -1;
- }
-
- pkt.pts = m_pts;
- pkt.dts = m_dts;
- pkt.duration = m_duration;
-
- PACKET_INFO info;
- get_av_packet_info(pkt, w, h, PACKET_TYPE_VIDEO, m_frame_pts_diff_v, info);
-
- m_real_data_h264_fun(&info, m_user_id);
-
- av_packet_unref(&pkt);
-
- return 1;
- }
写入共享内存
实现共享内存,主要是因为rtsp有终端访问个数的限制,比如我们用的雄迈相机,只能有3个终端访问,所以增加了共享内存这个功能,让其他终端通过共享内存来实时获取音视频流。
音视频同步到文件
创建文件(这里通过rtsp音视频流信息来创建文件信息)
- int ff_file::create_file(const char *file_name)
- {
-
- avformat_alloc_output_context2(&p_ofmt_ctx, NULL, NULL, file_name);
-
- if (!p_ofmt_ctx)
- {
- printf("Could not create output context\n");
- m_ret = AVERROR_UNKNOWN;
- return -1;
- }
- p_ofmt = p_ofmt_ctx->oformat;
-
- if (!p_ifmt_ctx)
- return -1;
-
- for (unsigned int i = 0; i < p_ifmt_ctx->nb_streams; i++)
- {
- AVMediaType type = p_ifmt_ctx->streams[i]->codecpar->codec_type;
- if (type == AVMEDIA_TYPE_VIDEO)
- {
- m_vi_stream_in = i;
- }
- else if (type == AVMEDIA_TYPE_AUDIO)
- {
- m_ai_stream_in = i;
- }
-
- AVStream *in_stream = p_ifmt_ctx->streams[i];
-
- AVCodec *codec = avcodec_find_decoder(in_stream->codecpar->codec_id);
- // 开辟流通道
- AVStream *out_stream = avformat_new_stream(p_ofmt_ctx, codec);
- if (!out_stream)
- {
- printf("Failed allocating output stream");
- m_ret = AVERROR_UNKNOWN;
- return -1;
- }
-
- if (type == AVMEDIA_TYPE_VIDEO)
- {
- m_vi_stream_out = out_stream->index;
- }
- else if (type == AVMEDIA_TYPE_AUDIO)
- {
- m_ai_stream_out = out_stream->index;
- }
-
- AVCodecContext *codecCtx = avcodec_alloc_context3(codec);
- m_ret = avcodec_parameters_to_context(codecCtx, in_stream->codecpar);
- if (m_ret < 0)
- {
- printf("avcodec_parameters_to_context failed..");
- return -1;
- }
- codecCtx->codec_tag = 0;
-
- if (type == AVMEDIA_TYPE_VIDEO)
- codecCtx->has_b_frames = 0;
-
- if (p_ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
- codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
-
- if (type == AVMEDIA_TYPE_VIDEO)
- {
- int frame_rate = in_stream->avg_frame_rate.num / in_stream->avg_frame_rate.den;
- codecCtx->time_base = { 1, frame_rate };
- }
-
- m_ret = avcodec_parameters_from_context(out_stream->codecpar, codecCtx);
- if (m_ret < 0)
- {
- printf("avcodec_parameters_from_context failed..");
- return -1;
- }
- }
-
- av_dump_format(p_ofmt_ctx, 0, file_name, 1);
-
- if (!(p_ofmt->flags & AVFMT_NOFILE))
- {
- if (avio_open(&p_ofmt_ctx->pb, file_name, AVIO_FLAG_WRITE) < 0)
- {
- printf("Could not open output file '%s'", file_name);
- return -1;
- }
- }
-
- m_ret = avformat_write_header(p_ofmt_ctx, NULL);
- if (m_ret < 0)
- {
- printf("Error occurred when opening output file {0}\n", m_ret);
- return -1;
- }
- return 1;
- }
写入音视频
- int ff_file::write_packet_info(unsigned char *buffer, PACKET_INFO *info)
- {
-
- if (NULL == p_ifmt_ctx)
- return -1;
-
- AVPacket pkt;
- av_init_packet(&pkt);
- pkt.data = (unsigned char *)buffer;
- pkt.size = info->nPacketSize;
- pkt.pts = info->nPts;
- pkt.dts = info->nDts;
- pkt.duration = info->nDurtion;
- pkt.pos = -1;
- pkt.stream_index = info->nPacketType == 1001 ? m_vi_stream_out : m_ai_stream_out;
-
- AVStream *in_stream;
- AVStream *out_stream;
-
- if (pkt.stream_index == m_vi_stream_out)
- {
- in_stream = p_ifmt_ctx->streams[m_vi_stream_in];
- out_stream = p_ofmt_ctx->streams[m_vi_stream_out];
-
- pkt.pts = m_cur_pts_v + info->nFramePtsDiff;
- pkt.dts = pkt.pts;
- pkt.duration = info->nDurtion;
- m_cur_pts_v = pkt.pts;
-
- pkt.pts = av_rescale_q(pkt.pts, in_stream->time_base, out_stream->time_base);
- pkt.dts = av_rescale_q(pkt.dts, in_stream->time_base, out_stream->time_base);
- pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
-
- //m_frame_index_v++;
- }
- else
- {
- in_stream = p_ifmt_ctx->streams[m_ai_stream_in];
- out_stream = p_ofmt_ctx->streams[m_ai_stream_out];
-
- pkt.pts = m_cur_pts_a + info->nFramePtsDiff;
- pkt.dts = pkt.pts;
- pkt.duration = info->nDurtion;
- m_cur_pts_a = pkt.pts;
-
- pkt.pts = av_rescale_q(pkt.pts, in_stream->time_base, out_stream->time_base);
- pkt.dts = av_rescale_q(pkt.dts, in_stream->time_base, out_stream->time_base);
- pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
-
- //m_frame_index_a++;
- }
- if (av_interleaved_write_frame(p_ofmt_ctx, &pkt) == 0)
- {
- //if (info->nPacketType == 1001)
- // printf("write video packet Success. \n");
- //else
- // printf("write audio packet Success. \n");
- }
- av_packet_unref(&pkt);
- return 1;
- }
关闭文件
- int ff_file::close_file()
- {
-
- if (NULL == p_ofmt_ctx)
- {
- return 1;
- }
-
- av_write_trailer(p_ofmt_ctx);
-
- if (!(p_ofmt->flags & AVFMT_NOFILE))
- avio_close(p_ofmt_ctx->pb);
-
- avformat_free_context(p_ofmt_ctx);
-
- p_ofmt_ctx = NULL;
-
- return 1;
- }
- void Qffmpeg::set_pts(AVPacket &pkt)
- {
-
- if (pkt.stream_index == m_audio_st_index)
- {
-
- if (is_first_frame_a)
- {
- pkt.pts = pkt.pts - p_ifmt_ctx->streams[m_audio_st_index]->start_time;
- pkt.dts = pkt.pts;
- m_frame_pts_diff_a = 0;
- m_last_pts_a = 0;
- is_first_frame_a = false;
- return;
- }
-
- m_frame_pts_diff_a = pkt.pts - m_last_pts_a;
- m_last_pts_a = pkt.pts;
- return;
- }
-
- if (is_first_frame_v)
- {
- pkt.pts = pkt.pts - p_ifmt_ctx->streams[m_video_st_index]->start_time;
- pkt.dts = pkt.pts;
- m_frame_pts_diff_v = 0;
- m_last_pts_v = 0;
- is_first_frame_v = false;
- return;
- }
- m_frame_pts_diff_v = pkt.pts - m_last_pts_v;
- m_last_pts_v = pkt.pts;
- return;
- }
- void Qffmpeg::release()
- {
-
- av_dict_free(&p_optionsDict);
-
- if (NULL != p_videoDeCodecCtx)
- {
- avcodec_free_context(&p_videoDeCodecCtx);
- p_videoDeCodecCtx = NULL;
- }
-
- if (NULL != p_videoEnCodecCtx)
- {
- avcodec_free_context(&p_videoEnCodecCtx);
- p_videoEnCodecCtx = NULL;
- }
-
- if (NULL != p_ifmt_ctx)
- {
- avformat_close_input(&p_ifmt_ctx);
- p_ifmt_ctx = NULL;
- }
-
- is_first_frame_a = 1;
- is_first_frame_v = 1;
- }
https://download.csdn.net/download/haiyangyunbao813/13944415 有需要的小伙伴可以瞅瞅。
文章内容参考过很多文章,有些文章地址找不到了,如果有作者介意,留言给我,我会及时改正。
以上内容如果有不对的地方,欢迎大佬指正。
最后最后祝我大连,早日战胜疫情,大连必胜,海蛎子必胜。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。