H264编码实现

409 阅读3分钟

#include "video_recorder.h"

#include <unistd.h>

#include <string.h>

#include "libavformat/avformat.h"

#include "libavdevice/avdevice.h"

#include "libswscale/swscale.h"

#define V_WIDTH 1280

#define V_HEIGHT 720

static int rec_status = 0;

void set_video_status(int status) {

rec_status = status;

}

static

FILE* create_video_outfile(char *filename) {

char str[80] = "/Users/yutianfeng/Desktop/S_T/ffmpegTest/";

strcat(str, filename);

FILE *outfile = fopen(str, "wb+");

return outfile;

}

static

AVFormatContext* open_dev(void) {

int ret = 0;

char errors[1024] = {0, };

/// 上下文, 使前/后api 依赖同一个上下文

AVFormatContext *fmt_ctx = NULL;

AVDictionary *options = NULL;

/// [ [video device] : [audio device] ]

/// 0: 机器摄像头 : 第一个设备

/// 1: 桌面

char *devicename = "0";

/// 注册所有设备

avdevice_register_all();

/// 获取格式

/// 根据输入格式的名称查找AVInputFormat

AVInputFormat *iformat = av_find_input_format("avfoundation");

/// 只有视频才需要设置这里的参数 音频不需要

av_dict_set(&options, "video_size", "1280x720", 0);

av_dict_set(&options, "framerate", "30", 0);

av_dict_set(&options, "pixel_format", "nv12", 0);

/// context: 双指针, 传入指针的地址即可

/// options : x264解码的时候需要传入一些参数, 这里不需要传参数

ret = avformat_open_input(&fmt_ctx, devicename, iformat, &options);

if (ret < 0) {

av_strerror(ret, errors, sizeof(errors));

fprintf(stderr, "faild open device : [%d] %s \n", ret, errors);

return NULL;

}

return fmt_ctx;

}

static void open_encoder(int width, int height, AVCodecContext **codec_ctx) {

int ret = 0;

AVCodec *codec = NULL;

codec = avcodec_find_encoder_by_name("libx264");

// codec = avcodec_find_encoder(AV_CODEC_ID_H264);

if (!codec) {

printf("find encoder null \n");

exit(1);

}

*codec_ctx = avcodec_alloc_context3(codec);

if (!codec_ctx) {

printf("avcodec context null \n");

exit(1);

}

// sps / pps

(*codec_ctx) -> profile = FF_PROFILE_H264_HIGH_444; // 最高级别 压缩特性最好

(*codec_ctx) -> level = 50; // 5.0

// 设置分辨率

(*codec_ctx) -> width = width;

(*codec_ctx) -> height = height;

// GOP

(*codec_ctx) -> gop_size = 250; // 如果是实时传输, GOP不能设置这么大 ,出现丢包画面很难恢复

(*codec_ctx) -> keyint_min = 20; // 最小20帧 插入一个I帧

// B帧

(*codec_ctx) -> max_b_frames = 3; // optional GOP中B帧的最大数量

(*codec_ctx) -> has_b_frames = 1; // optional

// 参考帧

(*codec_ctx) -> refs = 3; // optional 参考帧的数量 解码时 缓冲数组的长度, 参考帧越多处理的越慢 但是还原性越好, 反之...

// 输入YUV格式 因为输出的肯定是420P

(*codec_ctx) -> pix_fmt = AV_PIX_FMT_YUV420P;

// 码率

(*codec_ctx) -> bit_rate = 600000; // 600kbps

(*codec_ctx) -> time_base = (AVRational){1, 25}; // 帧与帧之间的间隔是 time_base

(*codec_ctx) -> framerate = (AVRational){25, 1}; // 帧率

ret = avcodec_open2((*codec_ctx), codec, NULL);

if (ret < 0) {

printf("codec open error[%d]:%s\n", ret, strerror(ret));

exit(1);

}

}

static

AVFrame* create_video_frame(int width, int height) {

int ret = 0;

// 音频输入数据

AVFrame *frame = NULL;

frame = av_frame_alloc();

if (!frame) {

printf("error, no memory!");

goto __ERROR;

}

frame -> width = width;

frame -> height = height;

frame -> format = AV_PIX_FMT_YUV420P;

// alloc inner buffer

ret = av_frame_get_buffer(frame, 32); // 视频中按32位对齐

if (ret < 0) {

printf("error, av_frame_get_buffer \n");

goto __ERROR;

}

return frame;

__ERROR:

if (frame) {

av_frame_free(&frame);

}

return NULL;

}

static void encode(AVCodecContext *codec_ctx, AVFrame *frame, AVPacket *newpkt, FILE *outfile) {

int ret = 0;

if (frame) {

printf("send a frame to encoder, pts = %lld \n", frame->pts);

}

// 向编码器输送数据

ret = avcodec_send_frame(codec_ctx, frame);

if (ret < 0) {

printf("error, failed to send a frame for encoding \n");

exit(1);

}

// 从编码器获取编码好的数据

while (ret >= 0) {

// avcodec_receive_frame(AVCodecContext *FF_API, AVFrame *frame); // 解码

// 编码:

ret = avcodec_receive_packet(codec_ctx, newpkt);

// 如果编码器数据不足或者到数据末尾时

if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {

printf("AVERROR \n");

return;

} else if (ret < 0) {

printf("error, failed to encode\n");

}

printf("fwrite~~~~~~~ \n");

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

av_packet_unref(newpkt);

}

}

void rec_video(void) {

av_log_set_level(AV_LOG_DEBUG);

int ret = 0;

int base = 0;

AVFormatContext *fmt_ctx = NULL;

AVCodecContext *codec_ctx = NULL;

AVFrame *frame = NULL;

AVPacket pkt; // 栈中分配

rec_status = 1;

FILE *outfile1 = create_video_outfile("video.yuv");

FILE *outfile2 = create_video_outfile("video.h264");

fmt_ctx = open_dev();

open_encoder(V_WIDTH, V_HEIGHT, &codec_ctx);

frame = create_video_frame(V_WIDTH, V_HEIGHT);

// 创建编码输出的packet

AVPacket *newpkt = av_packet_alloc();

if (!newpkt) {

printf("error , no new pakcet!!!");

goto __ERROR;

}

// read data from device

while (rec_status) {

ret = av_read_frame(fmt_ctx, &pkt);

int i = 0;

if (ret == -35) {

usleep(1000);

continue;

}

// nv12

// fwrite(pkt.data, 1, V_WIDTH * V_HEIGHT, outfile1);

// fflush(outfile1);

// uyvy422

// fwrite(pkt.data, 1, V_WIDTH * V_HEIGHT * 2, outfile1);

av_log(NULL, AV_LOG_INFO, "packet size is %d(%p) \n", pkt.size, pkt.data);

// NV12 YYYYYYYYUVUV

// YUV420P YYYYYYYYUUVV

memcpy(frame->data[0], pkt.data, V_WIDTH * V_HEIGHT); // copy Y data 先将Y拷贝到frame->data[0], 后面再写入文件 会出现Y分量的画面错乱的问题, 原因未知

// V_WIDTH * V_HEIGHT 之后是 UV 数据, 可以使用libyuv API实现此逻辑

for (i = 0; i < V_WIDTH * V_HEIGHT / 4; i++) {

frame->data[1][i] = pkt.data[V_WIDTH * V_HEIGHT + i * 2];

frame->data[2][i] = pkt.data[V_WIDTH * V_HEIGHT + i * 2 + 1];

}

fwrite(frame->data[0], 1, V_WIDTH * V_HEIGHT, outfile1);

fwrite(frame->data[1], 1, V_WIDTH * V_HEIGHT / 4, outfile1);

fwrite(frame->data[2], 1, V_WIDTH * V_HEIGHT / 4, outfile1);

frame -> pts = base++; // pts 连续使帧具有参考性, 否则就会画面花屏

encode(codec_ctx, frame, newpkt, outfile2);

av_packet_unref(&pkt);

}

encode(codec_ctx, NULL, newpkt, outfile2);

__ERROR:

if (fmt_ctx) {

avformat_close_input(&fmt_ctx);

}

if (outfile1) {

fclose(outfile1);

}

// if (outfile2) {

// fclose(outfile2);

// }

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

return;

}

总结 :

1、编码结束时 给编码器传一个null, 将编码器内的剩余数据全部输出.

2、一定要设置frame的pts值, 并使其连续增长, 否则这个值会是一个随机值导致画面马赛克.

3、NV12转YUV420P , 利用frame->data的数组结构, 分别存储Y、U、V分量, 按不同的顺序存储即可生成不同的格式.