视频编码器
基本步骤
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");
}