ios ffmpeg 實時視訊壓縮(主要是H264)
在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:即每秒播放多少幀。
四、進行編碼
<pre name="code" class="cpp">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 <stdio.h>
#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;
}