FFMpeg

视频编码器

基本步骤

1.打开编码器

转换NV12到YUV420P

准备编码数据AVFRAME

H264编码

// 格式名称,设备名称
#ifdef Q_OS_WIN
    // todo win下的配置根据电脑来, 因为win没有摄像头, 用Mac的来实现先
#else
    #define FMT_NAME "avfoundation"
    #define DEVICE_NAME "0:"
    #define VEDIO_SIZE "640x480"
    #define FRAMERATE "25"
    #define PIXEL_FORMAT "nv12"
#endif

#include "testVideo.h"
#include <time.h>
#include <unistd.h>
#include <string.h>
static int record_status = 0;
#define ms_sleep 5
#define V_WIDTH 640
#define V_HEIGHT 480
void vodeo_mssleep(unsigned long ms);

void setVideoRecordStatus(int status) {
    record_status = status;
}


/*@brief open audio device
 *@param
 *return
 */
AVFormatContext* open_voideo_device() {
    int ret = 0;
    size_t errbuf_size = 1024;
    char errors[1024] = {0,};
    AVFormatContext *fmt_ctx = NULL;
    AVDictionary *options = NULL;
    //[[video device]:[audio device]]
    //0代表mac机器摄像头
    //1代表桌面
    char *deviceName = "0:";
    av_dict_set(&options, "video_size", VEDIO_SIZE, 0);
    av_dict_set(&options, "framerate", FRAMERATE, 0);
    av_dict_set(&options, "pixel_format", PIXEL_FORMAT, 0);

    // getformat
    const AVInputFormat *iformat = av_find_input_format(FMT_NAME);
    // open device
    if ((ret = avformat_open_input(&fmt_ctx, deviceName, iformat, &options)) < 0) {
        
        av_strerror(ret, &errors, errbuf_size);
        
        printf(stderr,"Failed to open audio device,[%d] %s\n",ret,errors);
        
        avformat_close_input(&fmt_ctx);
        
        return NULL;
    }

    return fmt_ctx;
}

void vodeo_mssleep(unsigned long ms)
{
    struct timespec ts = {
          .tv_sec  = (long int) (ms / 1000),
          .tv_nsec = (long int) (ms % 1000) * 1000000ul
      };
    nanosleep(&ts, 0);
}

// 创建AVFRAME
AVFrame *create_video_frame(int width, int height) {
    int ret = 0;
    AVFrame *av_frame = NULL;
    av_frame = av_frame_alloc();/// 分配编码前的数据空间
    if(!av_frame) {
        printf("no memory");
        goto __ERROR;
    }
    av_frame->width = width;
    av_frame->height = height;
    av_frame->format = AV_PIX_FMT_YUV420P;
    // 视频按32位对齐
    ret = av_frame_get_buffer(av_frame, 32);
    if (ret < 0) {
        printf("Error Failed to alloc buf in frame");
        goto __ERROR;
    }
    return av_frame;
__ERROR:
    if (av_frame) {
        av_frame_free(&av_frame);
    }
    return NULL;
}

void encode_video(AVCodecContext *codec_ctx,
            AVFrame *av_frame,
            AVPacket *newPkt,
            FILE *file
            ) {
    if (!codec_ctx) {
        
    }
    if (!av_frame){
        
    }
    if (!newPkt) {
        
    }
    if (!file) {
        
    }

    
    
    int result = 0;
    ///将数据送编码器
    result = avcodec_send_frame(codec_ctx, av_frame);
    if (result < 0) {
        printf("Error failed to send a frame for encoding\n");
        exit(1);
    }
    
    ///如果ret>=0说明数据设置成功
    while (result >= 0) {
        ///如果编码器数据不足时会返回 EAGAIN 或者到结尾会返回AVERROR_EOF
        result = avcodec_receive_packet(codec_ctx, newPkt);
        if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) {
            break;
        } else if (result < 0) {
            printf("Error encoding video frame\n");
            exit(-1);
        }
        av_log(NULL,AV_LOG_INFO,"pkt size is  %d data is %p\n", newPkt->size,newPkt->data);

        /// 写文件
        /// (宽x高)x(yuv420=1.5/yuv422=2/yuv444=3)
        fwrite(newPkt->data, 1, newPkt->size, file);
        fflush(file);
        av_packet_unref(newPkt);
    }

    
    
}

void read_video_data_and_encode(AVFormatContext *fmt_ctx,
                                AVCodecContext *codec_ctx,
                                FILE *yuvFile,
                                FILE *outFile) {
    
    int ret = 0;
    int base = 0;
    AVPacket pkt;
    AVFrame *av_frame =  NULL;
    AVPacket *newpkt  = NULL;
    
    
    av_init_packet(&pkt);
    
    av_frame = create_video_frame(V_WIDTH, V_HEIGHT);
    
    if(!av_frame) {
        goto __ERROR;
    }
    /// 分配编码后的数据空间
    newpkt = av_packet_alloc();
    ///
    if(!newpkt) {
        goto __ERROR;
    }
    
    //read data from device
    while (((ret = av_read_frame(fmt_ctx, &pkt)) == 0 || ret == -35) && record_status) {
        
        if (ret == -35) {
            vodeo_mssleep(ms_sleep);
            continue;
        }
        
        //YYYYYYYYUVUV NV12
        //YYYYYYYYUUVV YUV420
        
        memcpy(av_frame->data[0], pkt.data, 307200);//y
        for (int i = 0; i < 307200/4; i++) {
            av_frame->data[1][i] = pkt.data[307200 + i *2];//u
            av_frame->data[2][i] = pkt.data[307200 + i *2 + 1];//v
        }
        
        fwrite(av_frame->data[0], 1, 307200, yuvFile);
        fwrite(av_frame->data[1], 1, 307200/4, yuvFile);
        fwrite(av_frame->data[2], 1, 307200/4, yuvFile);
        
        av_frame->pts = base++;
        encode_video(codec_ctx, av_frame, newpkt, outFile);
        av_packet_unref(&pkt);
        printf("camera working\n");
    }
    /// 强制将编码器缓冲区中的音频进行编码输出
    encode_video(codec_ctx, NULL, newpkt, outFile);
    
__ERROR:
    if (av_frame) {
        av_frame_free(&av_frame);
    }
    if (newpkt) {
        av_packet_free(&newpkt);
    }
}
/// 创建编码上下文 打开编码器
void open_video_coder(int width,int height,AVCodecContext **en_ctx) {
    const AVCodec *codec = NULL;
    int ret = 0;
    codec = avcodec_find_encoder(AV_CODEC_ID_H264);
//    codec =  avcodec_find_encoder_by_name("libx264");
    if (!codec) {
        printf("Codec libx264 not found\n");
        exit(1);
    }
    
    *en_ctx = avcodec_alloc_context3(codec);
    if (!en_ctx) {
        printf("enc_ctx  not found\n");
        exit(1);
    }
    //SPS
    (*en_ctx)->profile = FF_PROFILE_H264_HIGH;
    (*en_ctx)->level = 50;//level   5.0
    (*en_ctx)->width = width;
    (*en_ctx)->height = height;
    //gop
    //gop_size 设置小的话就会有多个gop 同时i帧就会很多 码流就会很大 设置大I帧少码流相对小 若出现丢包 就会等待时间长 因为gop大必须等到下一个i帧
    //gop第一帧是IDR帧
    (*en_ctx) ->gop_size = 250;
    (*en_ctx)->keyint_min=25;  //可选
    //设置b帧
    (*en_ctx)->max_b_frames = 3;//可选
    (*en_ctx)->has_b_frames = 1;//可选
    
    //参考帧的数量
    (*en_ctx)->refs = 3; //可选
    //设置输入的yuv格式 编码格式
    (*en_ctx)->pix_fmt = AV_PIX_FMT_YUV420P;
    
    //设置码率
    (*en_ctx)->bit_rate = 400000;//600kbps
    // 设置帧率
    (*en_ctx)->time_base = (AVRational){1,25};//帧与帧之间的间隔是 time_base
    (*en_ctx)->framerate = (AVRational){25,1};//帧率每秒25帧
    
    ret = avcodec_open2(*en_ctx, codec, NULL);
    if(ret < 0)
    {
        printf("avcodec_open2=============failed==%s\n",av_err2str(ret));
        exit(1);
    }
    
}

void rec_video() {
    AVFormatContext *fmt_ctx = NULL;
    AVCodecContext *codec_ctx = NULL;
    av_log_set_level(AV_LOG_DEBUG);
    /// 注册音视频设备
    avdevice_register_all();
    
    record_status = 1;
    
    /// 打开一个文件
    FILE *yuvFile = fopen("/Users/king/Desktop/ffmpeg/audio/o1.yuv", "wb+");
    FILE *outFile = fopen("/Users/king/Desktop/ffmpeg/audio/001.h264", "wb+");

    if (yuvFile == NULL) {
        printf("yuvFile文件创建失败");
        goto __ERROR;
    }
    if (outFile == NULL) {
        printf("outFile文件创建失败");
        goto __ERROR;
    }

    /// 打开设备AVInputFormat
    fmt_ctx = open_voideo_device();
    if (!fmt_ctx) {
        printf("fmt_ctx error");
        goto __ERROR;
    }
    
    //打开编码器
     open_video_coder(V_WIDTH,V_HEIGHT,&codec_ctx);
    if (!codec_ctx) {
        printf("codec_ctx error");
        goto __ERROR;
    }
    
    read_video_data_and_encode(fmt_ctx, codec_ctx,yuvFile,outFile);
    

__ERROR:
    if (codec_ctx) {
        avcodec_free_context(&codec_ctx);
    }
    
    if (fmt_ctx)  {
        avformat_close_input(&fmt_ctx);
    }
    
    if (outFile) {
        fclose(outFile);
    }
    av_log(NULL,AV_LOG_DEBUG,"record finish");
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值