ffmpeg 读取摄像头并通过rtmp推流同时在本地显示(新版ffmpeg接口+SDL2)

结合雷博的例子,将ffmpeg换成新版的接口。

看的其他博主的博客,终于勉强能用了。

遇到的坑:推流在本地通过SDL播放正常,推流收到的图片部分是花的,用ffmpeg命令却是正常的

ffmpeg -f dshow -i video="USB2.0 PC CAMERA" -vcodec libx264 -acodec copy -preset:v ultrafast -tune:v zerolatency -f flv rtmp://localhost:1935/live/room

开始是去掉

av_dict_set(¶m, "tune", "zerolatency", 0);

虽然解决了,但是这样延时很大

后来对比了下命令输出的信息,发现编码的方式不一样,雷博使用的是YUV420P  而命令采用的是YUV422P。

本地SDL播放用YUV420P,rtmp推流用YUV422P

本人新手入门,肯定有写的不够好的地方,恳请各位指正,相互提高。

废话不说上代码

#include #define __STDC_CONSTANT_MACROS#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
#include "libavutil/imgutils.h"
#include "libavutil/time.h"
#include "SDL.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include 
#include 
#include 
#include 
#include 
#ifdef __cplusplus
};
#endif
#endif//Output YUV420P 
#define OUTPUT_YUV420P  0
//'1' Use Dshow 
//'0' Use VFW
#define USE_DSHOW 1#define USE_SDL 1//Refresh Event
#define SFM_REFRESH_EVENT  (SDL_USEREVENT + 1)#define SFM_BREAK_EVENT  (SDL_USEREVENT + 2)int thread_exit = 0;int sfp_refresh_thread(void *opaque)
{thread_exit = 0;while (!thread_exit) {SDL_Event event;event.type = SFM_REFRESH_EVENT;SDL_PushEvent(&event);SDL_Delay(40);}thread_exit = 0;//BreakSDL_Event event;event.type = SFM_BREAK_EVENT;SDL_PushEvent(&event);return 0;
}//Show Dshow Device
void show_dshow_device() {AVFormatContext *ifmt_ctx = avformat_alloc_context();AVDictionary* options = NULL;av_dict_set(&options, "list_devices", "true", 0);AVInputFormat *iformat = av_find_input_format("dshow");printf("========Device Info=============\n");avformat_open_input(&ifmt_ctx, "video=dummy", iformat, &options);printf("================================\n");
}//Show Dshow Device Option
void show_dshow_device_option() {AVFormatContext *ifmt_ctx = avformat_alloc_context();AVDictionary* options = NULL;av_dict_set(&options, "list_options", "true", 0);AVInputFormat *iformat = av_find_input_format("dshow");printf("========Device Option Info======\n");avformat_open_input(&ifmt_ctx, "video=USB2.0 PC CAMERA", iformat, &options);printf("================================\n");
}//Show VFW Device
void show_vfw_device() {AVFormatContext *ifmt_ctx = avformat_alloc_context();AVInputFormat *iformat = av_find_input_format("vfwcap");printf("========VFW Device Info======\n");avformat_open_input(&ifmt_ctx, "list", iformat, NULL);printf("=============================\n");
}//Show AVFoundation Device
void show_avfoundation_device() {AVFormatContext *ifmt_ctx = avformat_alloc_context();AVDictionary* options = NULL;av_dict_set(&options, "list_devices", "true", 0);AVInputFormat *iformat = av_find_input_format("avfoundation");printf("==AVFoundation Device Info===\n");avformat_open_input(&ifmt_ctx, "", iformat, &options);printf("=============================\n");
}int main(int argc, char* argv[])
{AVOutputFormat *ofmt = NULL;AVFormatContext	*ifmt_ctx = NULL;AVFormatContext *ofmt_ctx = NULL;int				i, videoindex;AVCodecContext	*iCodecCtx;AVCodecContext	*oCodecCtx;AVStream		*video_st;AVCodec			*iCodec;AVCodec			*oCodec;//AVPacket		pkt;int				ret = -1;int				got_decpicture = -1;int				got_encpicture = -1;int frame_index = 0;const char  *out_filename;int64_t start_time = 0;//av_register_all();avformat_network_init();ifmt_ctx = avformat_alloc_context();//Open File//char filepath[]="src01_480x272_22.h265";//avformat_open_input(&ifmt_ctx,filepath,NULL,NULL)out_filename = "rtmp://localhost:1935/live/room";//Register Deviceavdevice_register_all();//Networkavformat_network_init();//Windows
#ifdef _WIN32//Show Dshow Deviceshow_dshow_device();//Show Device Optionsshow_dshow_device_option();//Show VFW Optionsshow_vfw_device();//输入部分
#if USE_DSHOWAVInputFormat *ifmt = av_find_input_format("dshow");//Set own video device's nameif (avformat_open_input(&ifmt_ctx, "video=USB2.0 PC CAMERA", ifmt, NULL) != 0) {printf("Couldn't open input stream.(无法打开输入流)\n");return -1;}
#elseAVInputFormat *ifmt = av_find_input_format("vfwcap");if (avformat_open_input(&ifmt_ctx, "0", ifmt, NULL) != 0) {printf("Couldn't open input stream.\n");return -1;}
#endif
#elif defined linux//LinuxAVInputFormat *ifmt = av_find_input_format("video4linux2");if (avformat_open_input(&ifmt_ctx, "/dev/video0", ifmt, NULL) != 0) {printf("Couldn't open input stream.\n");return -1;}
#elseshow_avfoundation_device();//MacAVInputFormat *ifmt = av_find_input_format("avfoundation");//Avfoundation//[video]:[audio]if (avformat_open_input(&ifmt_ctx, "0", ifmt, NULL) != 0) {printf("Couldn't open input stream.\n");return -1;}
#endifif (avformat_find_stream_info(ifmt_ctx, NULL)<0){printf("Couldn't find stream information.(无法获取流信息)\n");return -1;}videoindex = -1;for (i = 0; inb_streams; i++)if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){videoindex = i;break;}if (videoindex == -1){printf("Couldn't find a video stream.(没有找到视频流)\n");return -1;}//pCodecCtx = ifmt_ctx->streams[videoindex]->codec;iCodecCtx = avcodec_alloc_context3(NULL);avcodec_parameters_to_context(iCodecCtx, ifmt_ctx->streams[videoindex]->codecpar);iCodec = avcodec_find_decoder(ifmt_ctx->streams[videoindex]->codecpar->codec_id);if (iCodec == NULL){printf("Codec not found.(无法找到解码器编码)\n");return -1;}if (avcodec_open2(iCodecCtx, iCodec, NULL)<0){printf("Could not open codec.(无法打开解码器)\n");return -1;}//Dump Format------------------av_dump_format(ifmt_ctx, 0, "video=USB2.0 PC CAMERA", 0);//输出avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_filename);if (!ofmt_ctx) {printf("Could not create output context(不能创建输出上下文)\n");return -1;}//ofmt = ofmt_ctx->oformat;oCodec = avcodec_find_encoder(AV_CODEC_ID_H264);if (!oCodec) {printf("Can not find encoder! (没有找到合适的编码器!)\n");return -1;}oCodecCtx = avcodec_alloc_context3(oCodec);oCodecCtx->pix_fmt = AV_PIX_FMT_YUV422P;oCodecCtx->width = ifmt_ctx->streams[videoindex]->codecpar->width;oCodecCtx->height = ifmt_ctx->streams[videoindex]->codecpar->height;oCodecCtx->time_base.num = 1;oCodecCtx->time_base.den = 25;oCodecCtx->bit_rate = 400000;oCodecCtx->gop_size = 250;if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)oCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;//H264 codec param  //pCodecCtx->me_range = 16;  //pCodecCtx->max_qdiff = 4;  //pCodecCtx->qcompress = 0.6;  oCodecCtx->qmin = 10;oCodecCtx->qmax = 51;//Optional Param  oCodecCtx->max_b_frames = 3;// Set H264 preset and tune  AVDictionary *param = 0;av_dict_set(¶m, "preset", "ultrafast", 0);//解码时花屏,加上有花屏,去掉有延时av_dict_set(¶m, "tune", "zerolatency", 0);//av_dict_set(¶m, "rtbufsize", 3041280 , 0);if (avcodec_open2(oCodecCtx, oCodec, ¶m) < 0) {printf("Failed to open encoder! (编码器打开失败!)\n");return -1;}//Add a new stream to output,should be called by the user before avformat_write_header() for muxing  video_st = avformat_new_stream(ofmt_ctx, oCodec);if (video_st == NULL) {return -1;}video_st->time_base.num = 1;video_st->time_base.den = 25;//video_st->codec = oCodecCtx;avcodec_parameters_from_context(video_st->codecpar, oCodecCtx);//Open output URL,set before avformat_write_header() for muxing  if (avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_READ_WRITE) < 0) {printf("Failed to open output file! (输出文件打开失败!)\n");return -1;}//Dump Format------------------av_dump_format(ofmt_ctx, 0, out_filename, 1);//写文件头(Write file header)ret = avformat_write_header(ofmt_ctx, NULL);if (ret < 0) {printf("Error occurred when opening output URL\n");return -1;}//prepare before decode and encodeAVPacket *dec_packet = (AVPacket *)av_malloc(sizeof(AVPacket));AVPacket enc_packet;//AVPacket pkt;//int y_size = oCodecCtx->width * oCodecCtx->height;//av_new_packet(&enc_packet, y_size * 3);//转码上下文struct SwsContext *img_convert_ctx, *img_convert_ctx_SDL;img_convert_ctx = sws_getContext(iCodecCtx->width, iCodecCtx->height,iCodecCtx->pix_fmt, oCodecCtx->width, oCodecCtx->height, AV_PIX_FMT_YUV422P, SWS_BICUBIC, NULL, NULL, NULL);img_convert_ctx_SDL = sws_getContext(iCodecCtx->width, iCodecCtx->height,iCodecCtx->pix_fmt, oCodecCtx->width, oCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);AVFrame	*pFrame, *pFrameYUV, *pFrameYUV_SDL;pFrameYUV = av_frame_alloc();pFrameYUV_SDL = av_frame_alloc();//设置帧的格式、宽度和高度,否则会出现//1.AVFrame.format is not set//2.AVFrame.width or height is not setpFrameYUV->format = oCodecCtx->pix_fmt;pFrameYUV->width = oCodecCtx->width;pFrameYUV->height = oCodecCtx->height;pFrameYUV_SDL->format = AV_PIX_FMT_YUV420P;pFrameYUV_SDL->width = oCodecCtx->width;pFrameYUV_SDL->height = oCodecCtx->height;// 存储图像数据uint8_t *out_buffer;// av_image_get_buffer_size:返回使用给定参数存储图像所需的数据量的字节大小out_buffer = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV422P, oCodecCtx->width, oCodecCtx->height, 1));// 根据指定的图像参数和提供的数组设置数据指针和线条(data pointers and linesizes)av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer, AV_PIX_FMT_YUV422P, oCodecCtx->width, oCodecCtx->height, 1);// 存储图像数据uint8_t *out_buffer_SDL;// av_image_get_buffer_size:返回使用给定参数存储图像所需的数据量的字节大小out_buffer_SDL = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, oCodecCtx->width, oCodecCtx->height, 1));// 根据指定的图像参数和提供的数组设置数据指针和线条(data pointers and linesizes)av_image_fill_arrays(pFrameYUV_SDL->data, pFrameYUV_SDL->linesize, out_buffer_SDL, AV_PIX_FMT_YUV420P, oCodecCtx->width, oCodecCtx->height, 1);#if USE_SDL//SDL----------------------------if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {printf("Could not initialize SDL - %s\n", SDL_GetError());return -1;}int screen_w = 0, screen_h = 0;SDL_Window *screen;screen_w = oCodecCtx->width;screen_h = oCodecCtx->height;//screen = SDL_SetVideoMode(screen_w, screen_h, 0, 0);//SDL 2.0 Support for multiple windowsscreen = SDL_CreateWindow("Simplest FFmpeg Device", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,screen_w, screen_h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);if (!screen) {printf("SDL: could not set video mode - exiting:%s\n", SDL_GetError());return -1;}SDL_Texture *sdlTexture;//bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen);SDL_Renderer* sdlRenderer = SDL_CreateRenderer(screen, -1, 0);Uint32 pixformat = 0;//IYUV: Y + U + V  (3 planes)//YV12: Y + V + U  (3 planes)pixformat = SDL_PIXELFORMAT_IYUV;sdlTexture = SDL_CreateTexture(sdlRenderer, pixformat, SDL_TEXTUREACCESS_STREAMING, oCodecCtx->width, oCodecCtx->height);SDL_Rect rect;rect.x = 0;rect.y = 0;rect.w = screen_w;rect.h = screen_h;//SDL End------------------------//------------------------------SDL_Thread *video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);//SDL_Event event;
#endif // USE_SDLstart_time = av_gettime();for (;;){
#if USE_SDL//WaitSDL_WaitEvent(&event);if (event.type == SFM_REFRESH_EVENT) {
#endif//------------------------------if (av_read_frame(ifmt_ctx, dec_packet) >= 0){if (dec_packet->stream_index == videoindex){pFrame = av_frame_alloc();if (!pFrame) {printf("alloc pFrame Failed.\n");return -1;}//ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);ret = avcodec_send_packet(iCodecCtx, dec_packet);got_decpicture = avcodec_receive_frame(iCodecCtx, pFrame);if (ret < 0){av_frame_free(&pFrame);printf("Decode Error.\n");return -1;}if (got_decpicture == 0){//转码成YUV格式sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, iCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);sws_scale(img_convert_ctx_SDL, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, iCodecCtx->height, pFrameYUV_SDL->data, pFrameYUV_SDL->linesize);//初始化封装包enc_packet.data = NULL;enc_packet.size = 0;av_init_packet(&enc_packet);//编码//ret = avcodec_encode_video2(oCodecCtx, &enc_packet, pFrameYUV, &got_encpicture);ret = avcodec_send_frame(oCodecCtx, pFrameYUV);//FIX : non-strictly-monotonic PTS//pFrameYUV->pts++;AVRational time_base_in = ifmt_ctx->streams[videoindex]->time_base;//{ 1, 1000000 };time_base_in = iCodecCtx->time_base;//{ 1, 1000 };AVRational time_base_conert = { 1, AV_TIME_BASE };pFrameYUV->pts = av_rescale_q(dec_packet->pts, time_base_in, time_base_conert);if (ret < 0) {av_frame_free(&pFrame);printf("Encode Error.\n");return -1;}got_encpicture = avcodec_receive_packet(oCodecCtx, &enc_packet);if (got_encpicture == 0){frame_index++;enc_packet.stream_index = video_st->index;//FIX:No PTS (Example: Raw H.264)//Simple Write PTSif (enc_packet.pts == AV_NOPTS_VALUE){//Write PTSAVRational time_base1 = ifmt_ctx->streams[videoindex]->time_base;//Duration between 2 frames (us)int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);//Parametersenc_packet.pts = (double)(frame_index*calc_duration) / (double)(av_q2d(time_base1)*AV_TIME_BASE);enc_packet.dts = enc_packet.pts;enc_packet.duration = (double)calc_duration / (double)(av_q2d(time_base1)*AV_TIME_BASE);}//Write PTSAVRational time_base = ofmt_ctx->streams[video_st->index]->time_base;//{ 1, 1000 };AVRational r_framerate1 = ifmt_ctx->streams[videoindex]->r_frame_rate;// { 50, 2 };AVRational time_base_q = { 1, AV_TIME_BASE };//Duration between 2 frames (us)int64_t calc_duration = (double)(AV_TIME_BASE)*(1 / av_q2d(r_framerate1));	//内部时间戳//Parameters//enc_pkt.pts//enc_pkt.pts = (double)(framecnt*calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base));enc_packet.pts = av_rescale_q(frame_index*calc_duration, time_base_q, time_base);enc_packet.dts = enc_packet.pts;enc_packet.duration = av_rescale_q(calc_duration, time_base_q, time_base); //(double)(calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base));enc_packet.pos = -1;//Delayint64_t pts_time = av_rescale_q(enc_packet.dts, time_base, time_base_q);int64_t now_time = av_gettime() - start_time;if (pts_time > now_time)av_usleep(pts_time - now_time);//Print to Screenif (enc_packet.stream_index == videoindex) {printf("Send %8d video frames to output URL\n", frame_index);}//写到输出上下文ret = av_interleaved_write_frame(ofmt_ctx, &enc_packet);}av_packet_unref(&enc_packet);
#if USE_SDL//int y_size = iCodecCtx->width * iCodecCtx->height;SDL---------------------------设置纹理数据//pFrameYUV_SDL->data[0] = out_buffer;              // Y//pFrameYUV_SDL->data[1] = out_buffer + y_size;      // U //pFrameYUV_SDL->data[2] = out_buffer + y_size * 3 / 2;  // V#if 0SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
#elseSDL_UpdateYUVTexture(sdlTexture, &rect,pFrameYUV_SDL->data[0], pFrameYUV_SDL->linesize[0],pFrameYUV_SDL->data[1], pFrameYUV_SDL->linesize[1],pFrameYUV_SDL->data[2], pFrameYUV_SDL->linesize[2]);
#endif// 清理渲染器SDL_RenderClear(sdlRenderer);// 将纹理数据copy到渲染器//将sdlRect区域的图像显示到dstsdlRect区域//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &rect);// 显示SDL_RenderPresent(sdlRenderer);//SDL End-----------------------
#endif}av_frame_free(&pFrame);}av_packet_unref(dec_packet);}
#if USE_SDLelse{//Exit Threadthread_exit = 1;}
#endif
#if USE_SDL}else if (event.type == SDL_QUIT) {thread_exit = 1;}else if (event.type == SFM_BREAK_EVENT) {break;}
#endif}
#if USE_SDLSDL_Quit();
#endif//写文件尾(Write file trailer)av_write_trailer(ofmt_ctx);av_free(out_buffer);
end:if (video_st)avcodec_close(oCodecCtx);avformat_close_input(&ifmt_ctx);/* close output */if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))avio_close(ofmt_ctx->pb);avformat_free_context(ifmt_ctx);avformat_free_context(ofmt_ctx);if (ret < 0 && ret != AVERROR_EOF) {printf("Error occurred.\n");return -1;}return 0;
}

文件退出的部分没有测试,担心会有内存泄漏。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部