#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分量, 按不同的顺序存储即可生成不同的格式.