需求: Android 端把網絡攝像頭的一段正在播放的視頻流,截取保存成mp4(按錄像按鈕時開始錄像)。
實現: ffmpeg + x264 + sdl;
h264 裸流 打包成MP4,在網上也有一大堆文章,ffmpeg 也有一個muxing 的 example,大致流程都是一樣的,參考ffmpeg的muxing.c 就可以寫一個。我這裏把我在這個過程中遇到的問題寫出來,這些問題困擾了我很久才解決,誰叫我是視頻方面的小白呢。
這三個問題其實很簡單,但如果對這方面不瞭解的話,耗掉的時間還是很多的。
1,dts,pts:
在write_frame()之前,每一個avpacket 數據都要設置 dts,pts,因爲我的視頻沒有B幀,所以dts = pts 便可;pts 一開始我也不知道要怎麼設, 參考muxing.c,設置成寫入的幀數量便可(結合問題3);
2,sps,pps;
我接受到的裸流裏面 sps +pps + i幀 是放在一個NALU 裏面的,在保存成mp4時這個sps 非常重要,我一開始沒有設置,打包後的Mp4 普通的播放器就不能識別;
sps 在創建 編碼器時候傳遞給編碼器; sps 是 00 00 00 01 後面的,不包含這個00 00 00 01 這個碼;pps 我還沒用到。
case AVMEDIA_TYPE_VIDEO:
c->codec_id = codec_id;
LOGE("add_stream AVMEDIA_TYPE_VIDEO c->time_base.num = %d",ost->st->codec->time_base.num);
c->bit_rate = 400000;
/* Resolution must be a multiple of two. */
c->width = 352;
c->height = 288;
/* timebase: This is the fundamental unit of time (in seconds) in terms
* of which frame timestamps are represented. For fixed-fps content,
* timebase should be 1/framerate and timestamp increments should be
* identical to 1. */
ost->st->time_base = (AVRational){ 1, STREAM_FRAME_RATE};//STREAM_FRAME_RATE };
c->time_base =ost->st->time_base;
c->gop_size = 12; /* emit one intra frame every twelve frames at most */
c->pix_fmt = STREAM_PIX_FMT;
if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
/* just for testing, we also add B frames */
c->max_b_frames = 2;
}
if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) {
/* Needed to avoid using macroblocks in which some coeffs overflow.
* This does not happen with normal video, it just happens here as
* the motion of the chroma plane does not match the luma plane. */
c->mb_decision = 2;
}
c->extradata = spsinfo;
c->extradata_size = 10;
break;
3,fps:
在保存成mp4後,調試過程中各種fps都出來了,最後仔細看muxing.c ,發現其實要設置正確很簡單:在pts 按1遞增後,用一個很簡單的函數就解決了這個問題。
c->time_base : 輸入的幀率fps = 25 :{1,25},ost->st->time_base : 輸出的幀率; 我這裏設置和輸入的一樣就好了。
packet->pts = (ptsInc++);;// * (90000/STREAM_FRAME_RATE);
packet->dts = packet->pts;
// packet->duration = 8/1000000;
// packet->pos = -1;//ptsInc;
av_packet_rescale_ts(packet, c->time_base, ost->st->time_base);