实例 读取摄像头 封装FMP4 for linux
读取摄像头输出到文件 参考 - http://www.ffmpeg.org/ffmpeg-all.html#toc-fbdev
ffmpeg -s 320*240 -r 10 -i /dev/video0 testvideo.mp4
//ffplay 播放
ffplay -i /dev/video0C/C++ API
首先, 使用libavdevice的时候需要包含其头文件:
#include "libavdevice/avdevice.h"
然后, 在程序中需要注册libavdevice:
avdevice_register_all();
使用
AVFormatContext *avFormat = avformat_alloc_context();
AVInputFormat *ifmt=av_find_input_format("video4linux2");
int ret = avformat_open_input(&avFormat, "/dev/video0", ifmt, 0);
ret = avformat_find_stream_info(avFormat, 0);
printf("source find_stream_info= %d \n", ret);
printf("source >>>>>>>>>>>>>>>>>>>>\n");
av_dump_format(avFormat, 0, "",0 );
printf("source <<<<<<<<<<<<<<<<<<<<\n");
source >>>>>>>>>>>>>>>>>>>>
Input #0, video4linux2,v4l2, from '':
Duration: N/A, start: 5016.665747, bitrate: 115200 kb/s
Stream #0:0: Video: rawvideo (YUY2 / 0x32595559), yuyv422, 800x600, 115200 kb/s, 15 fps, 15 tbr, 1000k tbn, 1000k tbc
source <<<<<<<<<<<<<<<<<<<<拿到是 rawvideo 格式,无需解码可直接显示.
封装为Fragmented MP4 浏览器直播
从树莓派读取摄像头数据, 推送流到服务器, 服务器再websocket 分发到浏览器MSE api可播放的格式
FFMPEG结构体分析: AVFormatContext Mp4 必选与可选的box
读取摄像头输入
关键代码
...
int source_open( struct source_ctx *ctx) {
AVFormatContext *avFormat = avformat_alloc_context();
AVInputFormat *ifmt=av_find_input_format("video4linux2");
int ret = avformat_open_input(&avFormat, ctx->name, ifmt, 0);
if (_global_print_info ){
printf("source open_input= %d \n", ret);
}
ret = avformat_find_stream_info(avFormat, 0);
if (_global_print_info ){
printf("source find_stream_info= %d \n", ret);
printf(">>>>>>>>>source>>>>>>>>>>>\n");
av_dump_format(avFormat, 0, "",0 );
printf("<<<<<<<<<<<<<<<<<<<<\n");
}
for(int i=0; i<avFormat->nb_streams; i++) {
if(avFormat->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
ctx->video_index = i;
}else if(avFormat->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO){
ctx->audio_index = i;
}
}
AVCodec *codec = avcodec_find_decoder(avFormat->streams[ ctx->video_index]->codecpar->codec_id);
AVCodecContext *coder_ctx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(coder_ctx, avFormat->streams[ ctx->video_index]->codecpar);
ret = avcodec_open2(coder_ctx, codec, NULL);
if (_global_print_info ){
printf("source avcodec open2: %d \n", ret);
}
//AVFormatContext 和 AVCodecContext
ctx->codecCtx = coder_ctx;
ctx->avFormat = avFormat;
return 0;创建输出
关键代码
...
// source_ctx=输入参数结构体, target_ctx=输出参数结构体
int target_open( struct source_ctx *s_ctx, struct target_ctx *ctx){
...
//内存输出
ctx->ioBuffer = (unsigned char*)av_malloc(40960);
AVIOContext* avio = avio_alloc_context( ctx->ioBuffer, 40960 ,1, ctx, NULL, target_push,NULL);
AVOutputFormat *oformat = av_guess_format("mp4", NULL, NULL);
AVFormatContext *avFormat = NULL;
ret = avformat_alloc_output_context2(&avFormat,oformat,NULL,NULL);
avFormat->pb = avio;
avFormat->flags = AVFMT_FLAG_CUSTOM_IO;
AVStream *av_stream = avformat_new_stream(avFormat, NULL);
if (!av_stream) {
return -301;
}
// AVCodecParameters
AVCodecParameters *codecpar = av_stream->codecpar;
codecpar->codec_id = avFormat->oformat->video_codec;
codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
codecpar->format = AV_PIX_FMT_YUV420P;
codecpar->width = s_ctx->avFormat->streams[s_ctx->video_index]->codecpar->width;
codecpar->height = s_ctx->avFormat->streams[s_ctx->video_index]->codecpar->height;
// codecpar->width =400;
// codecpar->height = 300;
//坑! SPS PPS, 输出不然输出的mp4 会缺少avcC box
unsigned char sps_pps[] ={0x00,0x00,0x00,0x01,0x67,0x42,0xC0,0x1F,0xDA,0x03,0x20,0x4D,0xF9,0x61,0x00,0x00,0x03,0x00,0x01,0x00,0x00,0x03,0x00,0x28,0x8F,0x18,0x32,0xA0,
0x00,0x00,0x00,0x01,0x68,0xCE,0x3C,0x80};
int size_of = sizeof (sps_pps);
codecpar->extradata_size = size_of;
codecpar->extradata = (uint8_t*)av_malloc(size_of + AV_INPUT_BUFFER_PADDING_SIZE);
memcpy(codecpar->extradata, sps_pps, size_of);
AVCodec *codec = avcodec_find_encoder(av_stream->codecpar->codec_id);
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
ret = avcodec_parameters_to_context(codec_ctx, s_ctx->avFormat->streams[s_ctx->video_index]->codecpar);
if (_global_print_info ){
printf("target cpoy parameters_context: %d \n", ret);
}
if (ret != 0) {
//TODO
return -302;
}
//对一下参数, 也许对 AVCodecParameters 统一设置也行..
//FPS, 颜色格式, gop 等
codec_ctx->time_base = (AVRational){1, 10};
codec_ctx->codec_id = codec->id;
codec_ctx->pix_fmt = codecpar->format;
codec_ctx->gop_size = 20;
codec_ctx->width = codecpar->width ;
codec_ctx->height = codecpar->height ;
codec_ctx->bit_rate =5000*1000;
//重复的把SPS/PPS放到关键帧前面
codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
//参考下 [h264 编码低延迟]
av_opt_set(codec_ctx->priv_data,"tune","zerolatency",0);
av_opt_set(codec_ctx->priv_data,"preset","ultrafast",0);
//还是设置一下吧..
av_opt_set(codec_ctx->priv_data, "profile", "baseline", 0);
av_stream->codec->codec_tag = 0;
if (avFormat->oformat->flags & AVFMT_GLOBALHEADER){
av_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
AVDictionary* options = NULL;
//av_dict_set(&options,"tune","zerolatency",0);
//av_dict_set(&options,"preset","ultrafast",0);
ret = avcodec_open2(codec_ctx, codec, &options);
printf("target codec open: %d \n", ret);
if (_global_print_info ){
printf(">>>>>>>>>>target>>>>>>>>>>\n");
av_dump_format(avFormat, 0, "",1 );
printf("<<<<<<<<<<<<<<<<<<<<\n");
}
ctx->codecCtx = codec_ctx;
ctx->avFormat = avFormat;
}解封装循环
关键代码
int loop(struct source_ctx *s_ctx, struct target_ctx *t_ctx, struct push_ctx * ctx){
AVFormatContext *in_av_format = s_ctx->avFormat;
AVFormatContext *out_av_format = t_ctx->avFormat;
AVStream *in_stream = in_av_format->streams[s_ctx->video_index];
AVStream *out_stream = out_av_format->streams[0];
//
AVDictionary* options = NULL;
//封装 ffmp4
av_dict_set(&options, "movflags", "frag_keyframe+empty_moov+default_base_moof",0);
int result = avformat_write_header(out_av_format, &options);
AVPacket temp_packet, out_packet;
av_init_packet(&temp_packet);
av_init_packet(&out_packet);
int failCount = 0, data_count= 0;
//max fial count
const int failCountMax = 10;
//raw video 帧
AVFrame * out_frame= av_frame_alloc();
//转换颜色后的帧
AVFrame * convert_frame= av_frame_alloc();
av_image_alloc(convert_frame->data, convert_frame->linesize, t_ctx->codecCtx->width, t_ctx->codecCtx->height,AV_PIX_FMT_YUV420P, 4);
convert_frame->key_frame = out_frame->key_frame;
convert_frame->height = t_ctx->codecCtx->height;
convert_frame->width = t_ctx->codecCtx->width;
convert_frame->format = t_ctx->codecCtx->pix_fmt;
convert_frame->pkt_pos = -1;
int isFristEncodePkt = 0;
///
while(1){
//pull source
result = av_read_frame(in_av_format, &temp_packet);
if(result !=0){ continue; }
if(temp_packet.stream_index != s_ctx->video_index){
av_packet_unref(&temp_packet);
continue;
}
data_count++;
//新API 的解编码
///////////////////// decode ////////////////////////
result = avcodec_send_packet(s_ctx->codecCtx, &temp_packet);
if (_global_print_debug){
printf("push decode send_pack result=%d \n", result);
}
av_packet_unref(&temp_packet);
result = avcodec_receive_frame(s_ctx->codecCtx,out_frame);
if (_global_print_debug){
printf("push decode receive_frame result=%d \n", result);
}
if(result == 0){
//转换颜色
result = sws_scale(ctx->img_convert_ctx, out_frame->data, out_frame->linesize,
0, s_ctx->codecCtx->height, convert_frame->data, convert_frame->linesize);
//////////////// encode ////////////////////////
//转PTS
int64_t pts = av_rescale_q_rnd(out_frame->pts, in_stream->time_base, out_stream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
convert_frame->pts = pts;
// char filename_[100];
// sprintf(filename_, "/home/pi/data_count-%d.jpg",data_count);
// save_avframe(convert_frame, filename_);
result = avcodec_send_frame(t_ctx->codecCtx, convert_frame);
result = avcodec_receive_packet(t_ctx->codecCtx, &out_packet);
if (result == 0){
isFristEncodePkt = 1;
//push target
result = av_write_frame (out_av_format, &out_packet);
// result = av_interleaved_write_frame (out_av_format, &out_packet);
av_packet_unref(&out_packet);
}
}// end for if(result == 0){
}
result = av_write_trailer(out_av_format);
printf("push av_write_trailer result=%d,\n", result);
return result;踩坑指南
-
封装 ffmp4 需增加 flag
av_dict_set(&options, "movflags", "frag_keyframe+empty_moov+default_base_moof",0); -
输出FFMP4 没有宽高元素信息? 在Windows文件中也没有缩略图, 很明显缺少元数据, 能在Firefox的MSE API播放, 但在Chrome不能播放?
原因是没SPS, PPS信息!!! 跟这位老哥一模一样解决ffmpeg生成mp4文件不能正常预览的问题, mp4info工具打开则”open failed” 没有工具分析, 真恶心!
MP4Reader 工具可以打开分析; 另外自带的 ffprobe.exe工具也可以检查媒体信息, 灯下黑!
unsigned char sps_pps[] ={0x00,0x00,0x00,0x01,0x67,0x42,0xC0,0x1F,0xDA,0x03,0x20,0x4D,0xF9,0x61,0x00,0x00,0x03,0x00,0x01,0x00,0x00,0x03,0x00,0x28,0x8F,0x18,0x32,0xA0,
0x00,0x00,0x00,0x01,0x68,0xCE,0x3C,0x80};
int size_of = sizeof (sps_pps);
codecpar->extradata_size = size_of;
SPS/PPS的问题
另 如果要求 open 编码器以后 AVCodecContext extradata 没存有 SPS,PPS 信息需要加上
AVCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER
if(pFormatCtx->oformat->flags & AVFMT_GLOBALHEADER) {
pCodeCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER ;
}但, 直播流不建议加 设置了 AV_CODEC_FLAG_GLOBAL_HEADER 就会导致 x264_param_ t参数中的 b_repeat_header 赋值为0 ?
b_repeat_headers的意思是将SPS/PPS添加到每一个关键帧之前, 0代表不添加, 这样每个关键帧前面又没了SPS/PPS
H264 低延迟编码
编码延迟的问题
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
codec_ctx->time_base = (AVRational){1, 25};
//配置低延迟
av_opt_set(codec_ctx->priv_data, "preset", "ultrafast", 0);
av_opt_set(codec_ctx->priv_data, "tune","zerolatency",0);
av_opt_set(pCodeCtx->priv_data, "profile","baseline",0);
...
//avcodec_open2 打开编码器时 preset 选项 (OR?)
AVDictionary* options = NULL;
av_dict_set(&options,"preset","ultrafast",0);
ret = avcodec_open2(codec_ctx, codec,options);
...
// 写帧时 使用 av_write_frame API; 相对于 av_interleaved_write_frame ,它直接将包写进Mux, 没有缓存和重新排序
result = av_write_frame (out_av_format, &out_packet);
lib x264 选项 H264-options
--preset的参数主要调节编码速度和质量的平衡--tune的参数主要配合视频类型和视觉优化的参数, tune 的值有: film: 电影, 真人类型;
animation: 动画;
grain: 需要保留大量的grain时用;
stillimage: 静态图像编码时使用;
psnr: 为提高psnr做了优化的参数;
ssim: 为提高ssim做了优化的参数;
fastdecode: 可以快速解码的参数;
zerolatency: 零延迟, 用在需要非常低的延迟的情况下, 比如电视电话会议的编码;profilebaseline, 支持I/P 帧, 只支持无交错(Progressive)和CAVLC, 一般用于低阶或需要额外容错的应用 main, high, high10, high422, high444
实例 读取摄像头 for win
命令行工具
要安装screen-capture-recorder, 否则查看device会提示没有;
列出设备列表
ffmpeg -list_devices true -f dshow -i dummy
[dshow @ 00000211790f8c00] DirectShow video devices (some may be both video and audio devices)
[dshow @ 00000211790f8c00] "Digieye"
[dshow @ 00000211790f8c00] Alternative name "@device_pnp_\\?\usb#vid_eb1a&pid_2570&mi_00#6&327e6b33&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global"
//获取指定视频采集设备支持的分辨率, 帧率和像素格式等属性
ffmpeg -list_options true -f dshow -i video="Digieye"
//录制为文件
ffmpeg -f dshow -i video="Digieye" win_cam.mp4
//使用 ffplay 播放
ffplay -f dshow -i video="Digieye"
C/C++ API
wcl_32 无人车视频项目, 移植到win测试性能, 记录与win平台的差异代码..
读取摄像头 参考
AVFormatContext *avFormat = avformat_alloc_context();
AVInputFormat *ifmt=av_find_input_format("dshow");
char *device_name = "video=Digieye" ;
//int ret = avformat_open_input(&avFormat, ctx->name, ifmt, 0);
int ret = avformat_open_input(&avFormat, device_name, ifmt, 0);
if (_global_print_info ){
printf("source open_input= %d \n", ret);
}
windown API 的 socket 编程
通过 WSAGetLastError 获得错误码
win socket 错误码描述
int tcpClientInit(const char *ip, const uint16_t port, SOCKET *fd)
{
WORD sockVersion = MAKEWORD(2,2);
WSADATA data;
if(WSAStartup(sockVersion, &data) != 0)
{
return -1;
}
//创建套接字
*fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
//向服务器发起请求
struct sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
sockAddr.sin_family = PF_INET;
sockAddr.sin_addr.s_addr = inet_addr(ip);
sockAddr.sin_port = htons(port);
int ret = connect(*fd, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
return ret;
}
int tcpClientClose(SOCKET sockfd)
{
WSACleanup();
return closesocket(sockfd);
}
int tcpClientRecv(int sockfd, char* buf, size_t len)
{
return recv(sockfd, buf, MAXBYTE, NULL);
}
int tcpClientSend(SOCKET sockfd, char* buf, size_t len)
{
return send(sockfd, buf, len, NULL);
}char data[255] ;
data[0] = '\0';
const char * TEMPLATE1 = "{ \"id\":\"";
const char * TEMPLATE2 = "\", \"type\": \"wcl\",\"token\": \"abc\"}";
strcat(data, TEMPLATE1);
strcat(data, ctx->id);
strcat(data, TEMPLATE2);
size_t len = strlen(data);