ffmpeg编码之从 摄像头获取数据到yuv420p在到H264编码(4.x)

开始先看看雷神的这个图

在这里插入图片描述
最少我最近搞的编码解编码封装解封装这个图通用了

几个主要函数 和 结构体 (重复)

感觉还得写仔细点 要不然忘得太快了
AVFormatContext

输入或者说是获取到的文件操作者 主要用于存储音视频封装格式中包含的信息,基本所有音视频工程都要有的参数 同时,它也是我们进行解封装的功能结构体

AVCodecContext

一个十分庞大的结构体 主要有作用于编码 大多数时候在创建完成AVcodec后直接创建 是结构体中的的 AVCodec *codec 就是编码所采用的编码器包含着各种参数 例如宽高,帧率,数据类型,帧类型 等等,存入音频的基本参数,声道,采样率等。

AVCodec

编码器 由avcodec_find_encoder_by_name(“xxx”)创建
我目前用这个因为记不住编号
之后将编码器传入 avcodec_alloc_context3() 就可以创建一个新的 AVCodecContext 对象!!!!

AVFrame

一般用于存储未压缩的数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM) 此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表,QP表,运动矢量表等数据。编码的时候也存储了相关的数据。因此在使用FFMPEG进行码流分析的时候,AVFrame是一个很重要的结构体。

编码三要素
    codec = avcodec_find_encoder_by_name("libx264");
    ret = avcodec_open2((*enc_ctx), codec, NULL);
    ret = avcodec_encode_video2(c, &pkt, frame, &got_output);)//  ret = avcodec_send_frame(enc_ctx, frame);
函数步骤
一 初始化 AVFormatContext

1 avdevice_register_all()
2 AVInputFormat *iformat = av_find_input_format(“avfoundation”); //设定附带信息
3 ret = avformat_open_input(&fmt_ctx, devicename, iformat, &options)//初始化 avformatcontext

二 open_encoder

1 codec = avcodec_find_encoder_by_name(“libx264”);//通过名称创建
2 enc_ctx = avcodec_alloc_context3(codec);// 创建编码器
3 ret = avcodec_open2((*enc_ctx), codec, NULL);//打开编码器

三 create AVframe

1 frame = av_frame_alloc(); // 创建原始数据帧接收数据
2 ret = av_frame_get_buffer(frame, 32); //创建avframe 缓冲区 按 32 位对齐

四 encode

//送原始数据给编码器进行编码
ret = avcodec_send_frame(enc_ctx, frame);
//从编码器获取编码好的数据
while(ret >=0) {
ret = avcodec_receive_packet(enc_ctx, newpkt);

//



//  testc.c
//  myapp
//
//  Created by lichao on 2020/1/30.
//  Copyright © 2020年 lichao. All rights reserved.
//

#include "testc.h"
#include <string.h>


#define V_WIDTH 640
#define V_HEIGTH 480

static int rec_status = 0;

void set_status(int status){
    rec_status = status;
}

//@brief
//return
static AVFormatContext* open_dev(){
    
    int ret = 0;
    char errors[1024] = {0, };
    
    //ctx
    AVFormatContext *fmt_ctx = NULL;
    AVDictionary *options = NULL;
    
    //[[video device]:[audio device]]
    //0: 机器的摄像头
    //1: 桌面
    char *devicename = "0";
    
    //register audio device
    avdevice_register_all();
    
    //get format
    AVInputFormat *iformat = av_find_input_format("avfoundation");
    
    av_dict_set(&options, "video_size", "640x480", 0);
    av_dict_set(&options, "framerate", "30", 0);
    av_dict_set(&options, "pixel_format", "nv12", 0);
    
    //open device
    if((ret = avformat_open_input(&fmt_ctx, devicename, iformat, &options)) < 0 ){
        av_strerror(ret, errors, 1024);
        fprintf(stderr, "Failed to open video device, [%d]%s\n", ret, errors);
        return NULL;
    }
    
    return fmt_ctx;
}

/**
 * @brief xxxx
 * @param[in] xxx
 * @param[in] xxx
 * @param[out] xxx
 */
static void open_encoder(int width,
                         int height,
                         AVCodecContext **enc_ctx){
    
    int ret = 0;
    AVCodec *codec = NULL;
    
    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, 25}; //帧与帧之间的间隔是time_base
    (*enc_ctx)->framerate = (AVRational){25, 1}; //帧率,每秒 25 帧
    
    ret = avcodec_open2((*enc_ctx), codec, NULL);
    if(ret<0){
        printf("Could not open codec: %s!\n", av_err2str(ret));
        exit(1);
    }
}

/**
 * @brief xxxx
 * @param[in] width,
 * @param[in] height
 * @return AVFrame*
 */
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(!enc_ctx){
//        //xxxx
//    }
//
//    if(!frame){
//
//    }
    
    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);
        av_packet_unref(newpkt);
    }
}

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

    //pakcet
    AVPacket pkt;
    AVFormatContext *fmt_ctx = NULL;
    AVCodecContext *enc_ctx = NULL;
    
    //set log level
    av_log_set_level(AV_LOG_DEBUG);
    
    //start record
    rec_status = 1;
    
    //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_HEIGTH, &enc_ctx);
    
    //创建 AVFrame
    AVFrame* frame = create_frame(V_WIDTH, V_HEIGTH);
    
    //创建编码后输出的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 || ret == -35) &&
          rec_status) {
        if(ret==-35) {
               usleep(100);
               continue;
           }
        int i =0;
        
        av_log(NULL, AV_LOG_INFO,
               "packet size is %d(%p)\n",
               pkt.size, pkt.data);
       
        //(宽 x 高)x (yuv420=1.5/yuv422=2/yuv444=3)
//        fwrite(pkt.data, 1, 460800, yuvoutfile);
//        fflush(yuvoutfile);
        
        //YYYYYYYYUVVU NV12
        //YYYYYYYYUUVV YUV420
        memcpy(frame->data[0], pkt.data, 307200); //copy Y data
        //307200之后,是UV
        for(i=0; i < 307200/4; i++){
            frame->data[1][i] = pkt.data[307200+i*2];
            frame->data[2][i] = pkt.data[307201+i*2];
        }
        
        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;
}

#if 0
int main(int argc, char *argv[])
{
    rec_audio();
    return 0;
}
#endif

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
敬告:该系列的课程在抓紧录制更新中,敬请大家关注。敬告:本课程项目仅供学习参考,请不要直接商用,概不负责任何法律责任。 该系列的课程涉及:FFmpeg,WebRTC,SRS,Nginx,Darwin,Live555,等。包括:音视频、流媒体、直播、Android、视频监控28181、等。 我将带领大家一起来学习使用FFmpeg开发视频监控项目,并动手操练。具体内容包括: 一、视频监控的架构和流程二、FFmpeg4.3+SDL2+Qt5开发环境的搭建三、FFmpeg的SDK编程回顾总结并操练四、SDL2.0的编程回顾总结并操练五、颜色空间转换RGB和YUV的原理与实战六、Qt5+FFmpeg本地摄像头采集预览实战七、代码封装:摄像头h264/5编码并存储八、Qt5+FFmpeg单路网络摄像头采集预览九、Qt5+FFmpeg单路网络摄像头采集预览录制会看十、onvif与GB/T-28181的简介  音视频与流媒体是一门很复杂的技术,涉及的概念、原理、理论非常多,很多初学者不学 基础理论,而是直接做项目,往往会看到c/c++的代码时一头雾水,不知道代码到底是什么意思,这是为什么呢?   因为没有学习音视频和流媒体的基础理论,就比如学习英语,不学习基本单词,而是天天听英语新闻,总也听不懂。 所以呢,一定要认真学习基础理论,然后再学习播放器、转码器、非编、流媒体直播、视频监控、等等。   梅老师从事音视频与流媒体行业18年;曾在永新视博、中科大洋、百度、美国Harris广播事业部等公司就职,经验丰富;曾亲手主导广电直播全套项目,精通h.264/h.265/aac,曾亲自参与百度app上的网页播放器等实战产品。  目前全身心自主创业,主要聚焦音视频+流媒体行业,精通音视频加密、流媒体在线转码快编等热门产品。  

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值