在xcode上实现 iphone 实时传输当前画面的功能,本文件针对 h264 编码,如果需要其他编码 ,可以把video_encode_frame_init()里面的 编码id替换掉
1、 ffmpeg 视频流文件
使用之前编译好ffmpeg 库,并且加载了x264的库,然后将下面代码拷贝到一个空白的源文件中,它再次封装了视频流处理的四个流程。
一、 注册编码器
void video_encode_register();
注册所有编码(当然前提是你的ffmpeg库加载了所有的编码器),放在程序启动时执行,只需执行一次。
void video_encode_init();
初始化参数。
三、配置参数
void video_encode_frame_init(int gop_size,int bitrate,int frames_per_second_,int width,int height);
配置编码参数。需要在抓屏开始之前设置
关键参数:
bit_rate:比特率,即每秒种播放视频的比特数。
keyint_min:最小关键帧(I-frame)间隔
gop_size:关键帧间隔,也是最大间隔
max_b_frames:最大B-frame个数,与gop_size配合使用。等于0表示无B-frame
关键帧即key frame,也就是处于关键地位的。比如关键帧间隔是5,第1帧是关键帧,则第6帧也是关键帧,那么第1、6帧如果单独取出来保存就是一张完整的图片,而从第2~5帧只是存储了与第1帧的差别,这样减小了帧总体数据的大小。一般来说 关键帧越大,最终视频的数据越小。但太大了很可能导致中间出现很多失真的画面。
pix_fmt =AV_PIX_FMT_YUV420P; 帧格式,这里只能是这个,因为h264格式只能出来 yuv420p 的图片,所以如果RGBA的图片,需要进行转换。
width,height:帧的长宽。
time_base:即每秒播放多少帧。
四、进行编码
int video_encode_process(AVPacket *pkt,const uint8_t* imgbuf,int cw,int ch);
每隔一段时间抓屏一次,并将获取的图片数据保存,应该是bmp 位图,其他格式图片存在文件头,矢量图数据保存之后不是视频
这时候cw,ch指的是图片实际的长宽,而不是帧的长宽,但是最终会转化成帧的长宽。不建议图片实际长宽与帧长宽不一致,因为那样图片会失真。建议如果图片长宽与实际长宽不一致,可以先转换成长宽一致的图片。比如用Qt里的 scaleTo()等方法,而不是 用ffmpeg的方法。
例如:
AVPacket pkt;
char *imgbuf=captureScreen();//抓屏函数,返回的是image 的首地址,这里不存在这个API,需要自己根据平台自己完成
if(video_encode_process(&pkt,imgbuf,320,480)>0)
{
save_file(pkt.data,pkt.len);//保存数据到一个文件中
av_pkt_free(&pkt);//释放pkt,因为data被申请了内存
}
五、编码结束处理
video_encode_end(AVPacket *pkt);
//
// h264video.c
// AppLinkTester2
//
// Created by on 15-5-20.
//
//
#include
#include "h264video.h"
AVFrame *pframe;
AVCodecContext *contex;
FILE *fhandle;
int frames_per_second;
void video_encode_register()
{
av_register_all();
avcodec_register_all();
contex=NULL;
pframe=NULL;
}
//31ms
void rgb32_to_yuv420(const uint8_t *rgb, AVFrame* frame,int cw,int ch)
{
printf("rgb32_to_yuv420 start:\n");
const uint8_t *rgb_src[3]= {rgb, NULL, NULL};
int rgb_stride[3]={4*cw, 0, 0};
struct SwsContext *yuvContext=sws_getContext(cw,ch,AV_PIX_FMT_RGBA,frame->width,frame->height,AV_PIX_FMT_YUV420P,SWS_BILINEAR, NULL, NULL, NULL);
int oh=sws_scale(yuvContext,rgb_src,rgb_stride, 0, ch, frame->data, frame->linesize);
if (oh!=frame->height) {
printf("rgb32 to yuv420 error\n");
}
sws_freeContext(yuvContext);
printf("rgb32_to_yuv420 end:\n");
}
//130ms 1
void video_encode_init()
{
printf("video_encode_init start:\n");
if (contex){
avcodec_close(contex);
av_free(contex);
}
if (pframe) {
av_free(pframe);
}
contex=NULL;
pframe=NULL;
printf("video_encode_init end:\n");
}
int video_frame_get_width()
{
return pframe->width;
}
int video_frame_get_height()
{
return pframe->height;
}
//gop_size 关键幀间隔 biterate 比特率 单位:kb/s
void video_encode_frame_init(int gop_size,int bitrate,int frames_per_second_,int width,int height)
{
AVCodec *codec=NULL;
frames_per_second=frames_per_second_;
codec=avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec) {
fprintf(stderr, "Codec not found\n");
return;
}
contex = avcodec_alloc_context3(codec);
if (!contex) {
fprintf(stderr, "Could not allocate video codec context\n");
return;
}
contex->bit_rate = bitrate*1000;
contex->keyint_min=0;
// contex->scenechange_threshold
contex->time_base=(AVRational){1,frames_per_second};
contex->gop_size =gop_size;
contex->max_b_frames = 0;
contex->delay=0;
contex->pix_fmt =AV_PIX_FMT_YUV420P;//
av_opt_set(contex->priv_data, "preset", "superfast", 0);//superfast
av_opt_set(contex->priv_data,"tune","zerolatency",0);
contex->width = ((int)(width/2))*2;
contex->height = ((int)(height/2))*2;
if(avcodec_open2(contex, codec, NULL)){
fprintf(stderr, "Could not open codec\n");
}
pframe = av_frame_alloc();
if (!pframe) {
fprintf(stderr, "Could not allocate video frame\n");
return;
}
// pframe->pict_type=AV_PICTURE_TYPE_NONE;
// pframe->key_frame=0;
pframe->format=contex->pix_fmt;//
pframe->width=contex->width;
pframe->height=contex->height;
int ret=av_image_alloc(pframe->data, pframe->linesize, contex->width, contex->height, contex->pix_fmt, 32);
if(ret < 0){
fprintf(stderr, "Couldn't alloc raw picture buffer\n");
return;
}
pframe->pts=0;
}
int video_encode_end(AVPacket *pkt)
{
// uint8_t endcode[]={0,0,1,0xb7};
// FILE *f=fopen("/Users/patrick/git/png_to_video_h264/test01/test03.264", "wb+");
av_init_packet(pkt);
pkt->data = NULL; // packet data will be allocated by the encoder
pkt->size = 0;
/* get the delayed frames */
for (int got_output = 1; got_output; ) {
fflush(stdout);
int ret = avcodec_encode_video2(contex, pkt, NULL, &got_output);
if (ret < 0) {
fprintf(stderr, "Error encoding frame\n");
return ret;
}
if (got_output) {
pframe->pts++;
printf("Write frame (size=%5d)\n", pkt->size);
// fwrite(pkt.data, 1, pkt.size, fhandle);
// [self sendPacket:pkt.data len:pkt.size];
// av_free_packet(&pkt);
}
}
// fwrite(endcode, 1, sizeof(endcode), fhandle);
avcodec_close(contex);
//
// [self sendPacket:endcode len:sizeof(endcode)];
printf("编码完成,共%d张%d幀\n",(int)pframe->pts,(int)pframe->pts);
printf("视频时间应该是%f秒\n",(1.0*pframe->pts/frames_per_second));
return pkt->size;
}
//6ms~ 300ms
int video_encode_process(AVPacket *pkt,const uint8_t* imgbuf,int cw,int ch)
{
printf("video_encode_process start:\n");
av_init_packet(pkt);
pkt->data = NULL; // packet data will be allocated by the encoder
pkt->size = 0;
int got_output=-1;
if(contex == NULL||pframe == NULL){
printf("encode contex is null\n");
return -1;
}
if(contex->codec_id==AV_CODEC_ID_NONE){
printf("contex is invalid");
return -1;
}
rgb32_to_yuv420(imgbuf,pframe,cw,ch);
fflush(stdout);
/* encode the image */
int ret = avcodec_encode_video2(contex, pkt, pframe, &got_output);
if (ret < 0) {
fprintf(stderr, "Error encoding frame\n");
return ret;
}
if (got_output) {
fflush(stdout);
pframe->pts++;
printf("Write frame %3d (size=%5d)\n", (int)pframe->pts, pkt->size);
if (pkt->flags&AV_PKT_FLAG_KEY) {
printf("key frame packet %d,pts=%ld",pframe->pts,pkt->pts);
}
}
//free(&pFrame->data[0]);
printf("video_encode_process end:\n");
return pkt->size;
}