linux下使用ffmpeg采集摄像头数据并编码成h264文件

本文详细介绍了在Linux环境下,如何利用ffmpeg命令行及编程方式采集USB摄像头数据,并使用libx264编码器将其编码为H264格式的视频文件。内容涵盖所需的软硬件环境、ffmpeg命令行用法以及C语言实现的代码示例。通过配置ffmpeg、打开设备、设置编码参数、转换和编码数据,最终将视频保存为.h264文件并验证播放效果。
摘要由CSDN通过智能技术生成

本文讲述如何在linux下,使用ffmpeg采集视频数据,并编码成h264文件。
打算分成3部分讲解:

  1. 需要具备的软硬件环境
  2. ffmpeg命令采集摄像头数据并编码成h264文件
  3. ffmpeg代码采集摄像头数据并编码成h264文件

需要具备的软硬件环境

需要具备的硬件环境,摄像头。我这里是一个usb 摄像头。
需要具备的软件环境,ffmpeg,并且启用了libx264编码的ffmpeg。假如是自己编译的ffmpeg,在configure 的时候,需要添加上参数“–enable-libx264”,我在这中间遇到了一些问题,老是找不到libx264编码器。后面做了2个操作,才完成的。

  1. 删除了原来老的ffmpeg(自己通过apt install安装的),及其相关的库(libavcodec等)。
  2. 自己安装了libx264-dev。然后configure 的时候加上了"–enable-libx264"参数,最后编译安装完成的。

ffmpeg命令采集摄像头数据并编码成h264文件

ffmpeg -f video4linux2 -i "/dev/video0" -vcodec libx264 -pix_fmt yuv420p mycamera.h264

使用上述命令,可以采集摄像头数据,并编码成h264文件。下面详细讲解一下这些参数。
-f video4linux2,表示采用video4linux2驱动程序。
-i “/dev/video0”,表示输入数据的地址是“/dev/video0”,这个设备地址,就是我们的摄像头设备地址。
-vcodec libx264,表示采用libx264编解码器。
-pix_fmt yuv420p,表示编码的数据采用yuv420p。
mycamera.h264,这个就是我们最终的编码好的h264数据。

然后使用命令播放一下,看看效果,没问题。

ffplay mycamera.h264

ffmpeg代码采集摄像头数据并编码成h264文件

分成几个部分,说明采集摄像头并编码成h264文件的过程。

  1. 打开设备
  2. 打开编码器
  3. 采集摄像头数据,为yuyv422格式
  4. 转换摄像头数据,从yuyv422转成yuv420p
  5. 编码yuv420p数据为h264数据,并写成文件

打开设备

    // ctx
    AVFormatContext *fmt_ctx = NULL;
    AVDictionary *options = NULL;
    char *devicename = "/dev/video0";
    // register audio device
    avdevice_register_all();
    // get format
    AVInputFormat *iformat = av_find_input_format("video4linux2");

    av_dict_set(&options, "video_size", "640x480", 0);
    av_dict_set(&options, "framerate", "30", 0);
    av_dict_set(&options, "pixel_format", "yuyv422", 0);

    // open device
    avformat_open_input(&fmt_ctx, devicename, iformat, &options);

这个,我们做了3件事,来打开设备:

  1. 使用video4linux2这个驱动
  2. 指定了设备路径为/dev/video0
  3. 指定了一些参数,video_size, framerate, pixel_format
    最后,使用avformat_open_input来打开设备。

打开编码器

    AVCodec *codec = NULL;
    codec = avcodec_find_encoder_by_name("libx264");
    AVCodecContext **enc_ctx = NULL;
    *enc_ctx = avcodec_alloc_context3(codec);
    
    // SPS/PPS
    (*enc_ctx)->profile = FF_PROFILE_H264_HIGH_444;
    (*enc_ctx)->level = 50; //表示LEVEL是5.0
    
    //设置分辫率
    (*enc_ctx)->width = width;   // 640
    (*enc_ctx)->height = height; // 480

    // GOP
    (*enc_ctx)->gop_size = 250;
    (*enc_ctx)->keyint_min = 25; // option

    //设置B帧数据
    (*enc_ctx)->max_b_frames = 3; // option
    (*enc_ctx)->has_b_frames = 1; // option

    //参考帧的数量
    (*enc_ctx)->refs = 3; // option

    //设置输入YUV格式
    (*enc_ctx)->pix_fmt = AV_PIX_FMT_YUV420P;

    //设置码率
    (*enc_ctx)->bit_rate = 600000; // 600kbps

    //设置帧率
    (*enc_ctx)->time_base = (AVRational){1, 30}; //帧与帧之间的间隔是time_base
    (*enc_ctx)->framerate = (AVRational){30, 1}; //帧率,每秒 30帧

    avcodec_open2((*enc_ctx), codec, NULL);

这里,也主要做了3件事:

  1. avcodec_find_encoder_by_name找到libx264这个编码器。
  2. 给编码器开辟了一个 编码器上下文,avcodec_alloc_context3,并对这个上下文设置了一些参数。这都是我们编码需要使用的参数。
  3. 使用avcodec_open2打开了编码器。

采集摄像头数据

    AVPacket pkt;
    while ( av_read_frame(fmt_ctx, &pkt) == 0) {
        int i = 0;
        av_log(NULL, AV_LOG_INFO, "packet size is %d(%p)\n", pkt.size,
               pkt.data);
	}

这个采集数据,只需要拿着第一步打开的设备上下文,传递给av_read_frame进行数据读取,就可以了。

转换摄像头数据,从yuyv422转成yuv420

void yuyv422ToYuv420p(AVFrame *frame, AVPacket *pkt) {
    int i = 0;
    int yuv422_length = V_WIDTH * V_HEIGHT * 2;
    int y_index = 0;
    // copy all y
    for (i = 0; i < yuv422_length; i += 2) {
        frame->data[0][y_index] = pkt->data[i];
        y_index++;
    }

    // copy u and v
    int line_start = 0;
    int is_u = 1;
    int u_index = 0;
    int v_index = 0;
    // copy u, v per line. skip a line once
    for (i = 0; i < V_HEIGHT; i += 2) {
        // line i offset
        line_start = i * V_WIDTH * 2;
        for (int j = line_start + 1; j < line_start + V_WIDTH * 2; j += 4) {
            frame->data[1][u_index] = pkt->data[j];
            u_index++;
            frame->data[2][v_index] = pkt->data[j + 2];
            v_index++;
        }
    }
}

我这里,摄像头读取的yuyv422存储在AVPacket中,然后将他转换成yuv420p,存放到AVFrame中。其中AVFrame的data[0]存放y数据,data[1]存放u数据,data[2]存放v数据。至于yuyv422和yuv420的存储方式,这里就不讲了,网上能搜索到的。

编码并保存为文件

static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *newpkt,
                   FILE *outfile) {
    int ret = 0;
    if (frame) {
        printf("send frame to encoder, pts=%lld", frame->pts);
    }
    //送原始数据给编码器进行编码
    ret = avcodec_send_frame(enc_ctx, frame);
    if (ret < 0) {
        printf("Error, Failed to send a frame for enconding!\n");
        exit(1);
    }

    //从编码器获取编码好的数据
    while (ret >= 0) {
        ret = avcodec_receive_packet(enc_ctx, newpkt);

        //如果编码器数据不足时会返回  EAGAIN,或者到数据尾时会返回 AVERROR_EOF
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            return;
        } else if (ret < 0) {
            printf("Error, Failed to encode!\n");
            exit(1);
        }

        fwrite(newpkt->data, 1, newpkt->size, outfile);
        fflush(outfile);
        av_packet_unref(newpkt);
    }
}

编码的话,主要通过2个步骤完成:

  1. avcodec_send_frame,将需要编码的AVFrame数据(就是上一步骤拿到的yuv420p的AVFrame数据),送给编码器。
  2. avcodec_receive_packet,将编码好的AVPacket数据,读取出来,并保存文件。

全部代码

#include <string.h>

#include "libavdevice/avdevice.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"

#define V_WIDTH 640
#define V_HEIGHT 480

//@brief
// return
static AVFormatContext *open_dev() {
    int ret = 0;
    char errors[1024] = {
        0,
    };

    // ctx
    AVFormatContext *fmt_ctx = NULL;
    AVDictionary *options = NULL;

    //摄像头的设备文件
    char *devicename = "/dev/video0";

    // register video device
    avdevice_register_all();

    // get format
    AVInputFormat *iformat = av_find_input_format("video4linux2");

    av_dict_set(&options, "video_size", "640x480", 0);
    av_dict_set(&options, "framerate", "30", 0);
    av_dict_set(&options, "pixel_format", "yuyv422", 0);

    // open device
    ret = avformat_open_input(&fmt_ctx, devicename, iformat, &options);
    if (ret < 0) {
        av_strerror(ret, errors, 1024);
        fprintf(stderr, "Failed to open video device, [%d]%s\n", ret, errors);
        return NULL;
    }

    return fmt_ctx;
}

static void open_encoder(int width, int height, AVCodecContext **enc_ctx) {
    int ret = 0;
    AVCodec *codec = NULL;
    avcodec_register_all();

    codec = avcodec_find_encoder_by_name("libx264");
    if (!codec) {
        printf("Codec libx264 not found\n");
        exit(1);
    }

    *enc_ctx = avcodec_alloc_context3(codec);
    if (!enc_ctx) {
        printf("Could not allocate video codec context!\n");
        exit(1);
    }

    // SPS/PPS
    (*enc_ctx)->profile = FF_PROFILE_H264_HIGH_444;
    (*enc_ctx)->level = 50; //表示LEVEL是5.0

    //设置分辫率
    (*enc_ctx)->width = width;   // 640
    (*enc_ctx)->height = height; // 480

    // GOP
    (*enc_ctx)->gop_size = 250;
    (*enc_ctx)->keyint_min = 25; // option

    //设置B帧数据
    (*enc_ctx)->max_b_frames = 3; // option
    (*enc_ctx)->has_b_frames = 1; // option

    //参考帧的数量
    (*enc_ctx)->refs = 3; // option

    //设置输入YUV格式
    (*enc_ctx)->pix_fmt = AV_PIX_FMT_YUV420P;

    //设置码率
    (*enc_ctx)->bit_rate = 600000; // 600kbps

    //设置帧率
    (*enc_ctx)->time_base = (AVRational){1, 30}; //帧与帧之间的间隔是time_base
    (*enc_ctx)->framerate = (AVRational){30, 1}; //帧率,每秒 30帧

    ret = avcodec_open2((*enc_ctx), codec, NULL);
    if (ret < 0) {
        printf("Could not open codec: %s!\n", av_err2str(ret));
        exit(1);
    }
}

static AVFrame *create_frame(int width, int height) {
    int ret = 0;
    AVFrame *frame = NULL;

    frame = av_frame_alloc();
    if (!frame) {
        printf("Error, No Memory!\n");
        goto __ERROR;
    }

    //设置参数
    frame->width = width;
    frame->height = height;
    frame->format = AV_PIX_FMT_YUV420P;

    // alloc inner memory
    ret = av_frame_get_buffer(frame, 32); //按 32 位对齐
    if (ret < 0) {
        printf("Error, Failed to alloc buffer for frame!\n");
        goto __ERROR;
    }

    return frame;

__ERROR:
    if (frame) {
        av_frame_free(&frame);
    }

    return NULL;
}

static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *newpkt,
                   FILE *outfile) {
    int ret = 0;
    if (frame) {
        printf("send frame to encoder, pts=%lld", frame->pts);
    }
    //送原始数据给编码器进行编码
    ret = avcodec_send_frame(enc_ctx, frame);
    if (ret < 0) {
        printf("Error, Failed to send a frame for enconding!\n");
        exit(1);
    }

    //从编码器获取编码好的数据
    while (ret >= 0) {
        ret = avcodec_receive_packet(enc_ctx, newpkt);

        //如果编码器数据不足时会返回  EAGAIN,或者到数据尾时会返回 AVERROR_EOF
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            return;
        } else if (ret < 0) {
            printf("Error, Failed to encode!\n");
            exit(1);
        }

        fwrite(newpkt->data, 1, newpkt->size, outfile);
        fflush(outfile);
        av_packet_unref(newpkt);
    }
}

void yuyv422ToYuv420p(AVFrame *frame, AVPacket *pkt) {
    int i = 0;
    int yuv422_length = V_WIDTH * V_HEIGHT * 2;
    int y_index = 0;
    // copy all y
    for (i = 0; i < yuv422_length; i += 2) {
        frame->data[0][y_index] = pkt->data[i];
        y_index++;
    }

    // copy u and v
    int line_start = 0;
    int is_u = 1;
    int u_index = 0;
    int v_index = 0;
    // copy u, v per line. skip a line once
    for (i = 0; i < V_HEIGHT; i += 2) {
        // line i offset
        line_start = i * V_WIDTH * 2;
        for (int j = line_start + 1; j < line_start + V_WIDTH * 2; j += 4) {
            frame->data[1][u_index] = pkt->data[j];
            u_index++;
            frame->data[2][v_index] = pkt->data[j + 2];
            v_index++;
        }
    }
}

void rec_video() {
    int ret = 0;
    int base = 0;
    int count = 0;

    // pakcet
    AVPacket pkt;
    AVFormatContext *fmt_ctx = NULL;
    AVCodecContext *enc_ctx = NULL;

    // set log level
    av_log_set_level(AV_LOG_DEBUG);

    // create file
    char *yuvout = "./video.yuv";
    char *out = "./video.h264";

    FILE *yuvoutfile = fopen(yuvout, "wb+");
    FILE *outfile = fopen(out, "wb+");

    //打开设备
    fmt_ctx = open_dev();

    //打开编码器
    open_encoder(V_WIDTH, V_HEIGHT, &enc_ctx);

    //创建 AVFrame
    AVFrame *frame = create_frame(V_WIDTH, V_HEIGHT);

    //创建编码后输出的Packet
    AVPacket *newpkt = av_packet_alloc();
    if (!newpkt) {
        printf("Error, Failed to alloc avpacket!\n");
        goto __ERROR;
    }

    // read data from device
    while (((ret = av_read_frame(fmt_ctx, &pkt)) == 0) && (count++ < 100)) {
        int i = 0;
        av_log(NULL, AV_LOG_INFO, "packet size is %d(%p)\n", pkt.size,
               pkt.data);

        // YUYVYUYVYUYVYUYV   YUYV422
        // YYYYYYYYUUVV YUV420
        yuyv422ToYuv420p(frame, &pkt);

        fwrite(frame->data[0], 1, 307200, yuvoutfile);
        fwrite(frame->data[1], 1, 307200 / 4, yuvoutfile);
        fwrite(frame->data[2], 1, 307200 / 4, yuvoutfile);

        frame->pts = base++;
        encode(enc_ctx, frame, newpkt, outfile);
        //
        av_packet_unref(&pkt); // release pkt
    }

    encode(enc_ctx, NULL, newpkt, outfile);

__ERROR:
    if (yuvoutfile) {
        // close file
        fclose(yuvoutfile);
    }

    // close device and release ctx
    if (fmt_ctx) {
        avformat_close_input(&fmt_ctx);
    }

    av_log(NULL, AV_LOG_DEBUG, "finish!\n");
    return;
}

int main(int argc, char *argv[]) {
    rec_video();
    return 0;
}

全部代码,我这里命名为encode_video.c,然后使用命令

gcc -g -O0 encode_video.c -lavformat -lavutil -lavdevice  -lavcodec -o encode_video

进行编译。然后运行./encode_video,录制视频,录制完的视频保存在video.h264中。最后播放一下,看看效果

ffplay video.h264

可以看到视频播放正常。

  • 12
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
使用ffmpeg采集摄像头数据并进行推流,你可以使用以下命令: 1. 首先,使用命令`ffmpeg -list_devices true -f dshow -i dummy`获取摄像头的名称\[1\]。 2. 然后,使用以下命令来获取视频流并推流: ``` ffmpeg -f dshow -i video="摄像头名称" -vcodec libx264 -acodec copy -preset:v ultrafast -tune:v zerolatency -f flv <推流地址> ``` 这个命令将会获取摄像头的视频流,并使用libx264编码器进行视频编码,音频则直接复制。推流地址是你要推送到的目标地址\[1\]。 另外,如果你使用的是Linux系统,可以使用以下命令来采集摄像头数据并推流: ``` ./ffmpeg -f video4linux2 -r 12 -s 640x480 -i /dev/video0 -vcodec libx264 -f flv rtmp://127.0.0.1:1935/live/live ``` 这个命令将会采集/dev/video0设备的视频流,并使用libx264编码器进行视频编码,然后将视频流推送到rtmp://127.0.0.1:1935/live/live地址\[2\]。 希望以上信息对你有帮助! #### 引用[.reference_title] - *1* [FFMPEG采集摄像头推流方法说明](https://blog.csdn.net/az44yao/article/details/98104615)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [linux FFMPEG 摄像头采集数据推流](https://blog.csdn.net/hanhui22/article/details/109842044)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值