ffmpeg 摄像头采集编码,保存到本地或推流 您所在的位置:网站首页 ffmpeg编码推流卡顿 ffmpeg 摄像头采集编码,保存到本地或推流

ffmpeg 摄像头采集编码,保存到本地或推流

2024-07-18 05:42| 来源: 网络整理| 查看: 265

1)初始化采集输入设备,设置编码器与图像转换上下文 #define CAMERA_DEV "/dev/video0" #define CAMERA_FMT AV_PIX_FMT_YUYV422 #define ENCODE_FMT AV_PIX_FMT_YUV420P typedef struct { uint8_t *buf[4]; int linesize[4]; int size; } cnv_t; typedef struct { AVFormatContext *fmt_ctx; // 输入设备 AVCodecContext *enc_ctx; // 编码器上下文 AVPacket *camera_packet; // 从设备中读取图像 AVFrame *yuvframe; // 原始数据帧 int frame_index; // 对应帧中的pts void *convert_ctx; // 转换上下文 cnv_t cnv_src, cnv_dest; // 转换前后 } AVInputDev; void get_rawframe(AVInputDev *input) { AVPacket *camera_packet = input->camera_packet; // 摄像头获取图像数据 av_read_frame(input->fmt_ctx, camera_packet); memcpy(input->cnv_src.buf[0], camera_packet->data, camera_packet->size); // 图像格式转化 sws_scale((struct SwsContext *)input->convert_ctx,\ (const uint8_t **)input->cnv_src.buf, input->cnv_src.linesize, \ 0, input->enc_ctx->height, input->cnv_dest.buf, input->cnv_dest.linesize); av_packet_unref(camera_packet); // 清理数据 input->yuvframe->data[0] = input->cnv_dest.buf[0]; input->yuvframe->data[1] = input->cnv_dest.buf[1]; input->yuvframe->data[2] = input->cnv_dest.buf[2]; input->frame_index++; input->yuvframe->pts = input->frame_index; } void convert_init(AVInputDev *input, int w, int h) { // 图像格式转化 struct SwsContext *sws_ctx; sws_ctx = sws_getContext(w, h, CAMERA_FMT, \ w, h, ENCODE_FMT, 0, NULL, NULL, NULL); cnv_t *yuy2 = &input->cnv_src; cnv_t *iyuv = &input->cnv_dest; yuy2->size = av_image_alloc(yuy2->buf, yuy2->linesize, w, h, CAMERA_FMT, 1); iyuv->size = av_image_alloc(iyuv->buf, iyuv->linesize, w, h, ENCODE_FMT, 1); input->convert_ctx = sws_ctx; } int VideoInput_Init(AVInputDev *input, int w, int h) { avdevice_register_all(); AVInputFormat *in_fmt = av_find_input_format("v4l2"); if (in_fmt == NULL) { printf("can't find_input_format\n"); return -1; } // 设置摄像头的分辨率 AVDictionary *option = NULL; char video_size[10]; sprintf(video_size, "%dx%d", w, h); av_dict_set(&option, "video_size", video_size, 0); AVFormatContext *fmt_ctx = NULL; if (avformat_open_input(&fmt_ctx, CAMERA_DEV, in_fmt, &option) printf("failed to find encoder\n"); return -1; } AVCodecContext *cod_ctx = avcodec_alloc_context3(cod); cod_ctx->pix_fmt = ENCODE_FMT; cod_ctx->width = w; cod_ctx->height = h; cod_ctx->time_base.num = 1; cod_ctx->time_base.den = 30; cod_ctx->bit_rate = 400000; cod_ctx->qmin = 10; cod_ctx->qmax = 51; cod_ctx->max_b_frames = 0; cod_ctx->thread_count = 4; cod_ctx->gop_size = 15; if (avcodec_open2(cod_ctx, cod, NULL) AVFormatContext *fmt_ctx; const char *filename; int have_video; int have_audio; AVStream *video_st; AVStream *audio_st; } AVOutputMedia; int get_video(const char *out_file, int w, int h, int nb_frames) { // 采集输入初始化 AVInputDev *input = malloc(sizeof(AVInputDev)); VideoInput_Init(input, w, h); // 输出初始化 AVOutputMedia media = { .fmt_ctx = NULL, .filename = out_file, .have_video = 1, .video_st = NULL, }; avformat_alloc_output_context2(&media.fmt_ctx, NULL, NULL, media.filename); media.video_st = avformat_new_stream(media.fmt_ctx, NULL); avcodec_parameters_from_context(media.video_st->codecpar, input->enc_ctx); // 打开输出文件,并写文件头 if ((media.fmt_ctx->oformat->flags & AVFMT_NOFILE) == 0) { if (avio_open(&media.fmt_ctx->pb, media.filename, AVIO_FLAG_WRITE) printf("failed to write header\n"); return -1; } AVPacket *packet = av_packet_alloc(); while (1) { // 从输入设备中获取一帧图像,并转化格式 get_rawframe(input); // 编码压缩 int ret = encodec_frame_to_packet(input->enc_ctx, input->yuvframe, packet); if (ret == EAGAIN) continue; else if (ret == EINVAL) break; // 时基转换,编码数据写入文件 av_packet_rescale_ts(packet, input->enc_ctx->time_base, media.video_st->time_base); if (av_interleaved_write_frame(media.fmt_ctx, packet) get_video(argv[1], 640, 480, 200); return 0; }

遇到过的问题:[mp4 @ 0x8b9e40] Could not find tag for codec none in stream #0, codec not currently supported in container

image-20210530115652216

意思是不能在容器的相应流中找到编码器信号,创建流后需要使用avcodec_parameters_from_context将编码器的参数传递给stream的codecpar字段。

3)添加录制视频时间: #define MEDIA_DURATION 10 // 在while循环中添加录制时间期限 if (av_compare_ts(input->yuvframe->pts, input->enc_ctx->time_base, MEDIA_DURATION, (AVRational){1, 1}) >= 0) { printf("capture %d s video finish.\n", MEDIA_DURATION); break; }

image-20210530120619109 image-20210530120737299

4)不保存编码图像数据,直接推流 // 从队列中取数据,推流 void *send_rtmp(void *p) { AVOutputMedia *media = (AVOutputMedia *)p; queue_add_user(media->video_que, READER_ROLE); while (1) { AVPacket *packet = dequeue(media->video_que); if (packet == NULL) break; if (av_interleaved_write_frame(media->fmt_ctx, packet) // 采集输入初始化 AVInputDev *input = malloc(sizeof(AVInputDev)); VideoInput_Init(input, w, h); // 输出初始化 AVOutputMedia media = { .fmt_ctx = NULL, .filename = url, .video_st = NULL, }; if (support_rtmp) { // 直接推流到服务器 avformat_alloc_output_context2(&media.fmt_ctx, NULL, "flv", media.filename); } else { // 保存(flv/mp4)文件到本地 avformat_alloc_output_context2(&media.fmt_ctx, NULL, NULL, media.filename); } av_dump_format(media.fmt_ctx, 0, media.filename, 1); media.video_st = avformat_new_stream(media.fmt_ctx, NULL); avcodec_parameters_from_context(media.video_st->codecpar, input->enc_ctx); // 创建推流线程 pthread_t tid; pthread_create(&tid, NULL, send_rtmp, (void *)&media); // 创建一个码流队列 media.video_que = create_queue_buffer(); queue_add_user(media.video_que, WRITER_ROLE); if ((media.fmt_ctx->oformat->flags & AVFMT_NOFILE) == 0) { if (avio_open(&media.fmt_ctx->pb, media.filename, AVIO_FLAG_WRITE) printf("failed to write header\n"); return -1; } while (1) { if (av_compare_ts(input->yuvframe->pts, input->enc_ctx->time_base, \ MEDIA_DURATION, (AVRational){1, 1}) >= 0) { printf("capture %d s video finish.\n", MEDIA_DURATION); break; } get_rawframe(input); AVPacket *packet = av_packet_alloc(); int ret = encodec_frame_to_packet(input->enc_ctx, input->yuvframe, packet); if (ret == EAGAIN) continue; else if (ret == EINVAL) break; av_packet_rescale_ts(packet, input->enc_ctx->time_base, media.video_st->time_base); enqueue(media.video_que, packet); } av_write_trailer(media.fmt_ctx); queue_del_user(media.video_que, WRITER_ROLE); pthread_cancel(tid); // 注销推流线程 pthread_join(tid, NULL); VideoInput_Destroy(input); return 0; } #define MEDIA_DURATION 20 #define OUT_FILE "out.flv" #define RTMP_URL "rtmp://127.0.0.1:1935/live/1234" int main(int argc, char const* argv[]) { int is_rtmp = atoi(argv[1]); if (is_rtmp == 1) get_video(1, RTMP_URL, 640, 480); else get_video(0, OUT_FILE, 640, 480); return 0; }

运行./a.out 0可将文件保存为out.flv或out.mp4;运行./a.out 1将编码数据推流。 image-20210530190122297

遗留问题:推流时在网页上看不了视频;使用ffplay或vlc,能播放rtmp流,但是延时较大。 image-20210530230845993 代码放这里:https://gitee.com/zhoujiabo/audio-and-video-development/tree/master/live 如果有大佬知道上面的问题如何解决,请务必留言指教!



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有