Ffmpeg 獲取USB Camera 視頻流


       本文講述的案例是如何通過Ffmpeg實現從USB Camera中獲取視頻流並將視頻流保存到MP4文件。

本文亦適用於從USB Camera 獲取視頻流並將視頻流轉發到rtmp服務的案例,二者基本的原理和流程

一樣,不同的僅僅是輸出上下文。

        首先撇開Ffmpeg說說基本的原理,一直覺得基本的原理是最重要的,理解了基本的原理即使出現

問題也能快速定位問題。USB Camera 對操作系統來說就是一個USB 設備,應用程序如何獲得設備的基本

信息又如何跟設備進行通信呢?答案是通過設備的驅動程序,當然應用程序不會直接調用設備的驅動程序,

因為沒有這個權限。一般來說,應用程序會調用操作系統API間接跟設備打交道。說到這里肯定有人會說從

USB設備獲取視頻流豈不是很麻煩?如果每一步都是我們自己做肯定很麻煩,幸好這個世界有庫存在,庫

可以解決很多問題。DShow 這個庫就實現了對USB Camera設備的封裝,對外暴露打開,取流,關閉等接口。

本文講的是如何通過Ffmpeg實現從USB Camera取流,是不是Ffmpeg調用了DShow封裝好的接口?是的,事

實就是這樣。說了半天只是從底層描述了Ffmpeg如何獲跟USB Camera打交道,下面給出從取流到保存成文

件的基本流程。

                          圖1   Ffmpeg取流保存文件基本流程

     圖1 展示了Ffmpeg 獲取USB Camera視頻流並保存到本地文件的基本流程,如果您看過我之前寫的博客應該知道

大的流程基本相同,不同的是之前大多數情況下的視頻源輸入的是視頻流地址,本案例視頻源輸入的是設備的名稱,

具體的名稱可以從設備管理器中查看。基本代碼如下:

一.打開設備獲取設備基本信息。需要注意的是輸入的視頻格式需要填寫dshow,參數rtbufsize用來設置緩沖大小,

如果緩沖小了可能會出現丟幀的情況。

int OpenInput(string inputUrl)
{
	inputContext = avformat_alloc_context();	
	lastReadPacktTime = av_gettime();
	inputContext->interrupt_callback.callback = interrupt_cb;
	 AVInputFormat *ifmt = av_find_input_format("dshow");
	 AVDictionary *format_opts =  nullptr;
	 av_dict_set_int(&format_opts, "rtbufsize", 18432000  , 0);

	int ret = avformat_open_input(&inputContext, inputUrl.c_str(), ifmt,&format_opts);
	if(ret < 0)
	{
		av_log(NULL, AV_LOG_ERROR, "Input file open input failed\n");
		return  ret;
	}
	ret = avformat_find_stream_info(inputContext,nullptr);
	if(ret < 0)
	{
		av_log(NULL, AV_LOG_ERROR, "Find input file stream inform failed\n");
	}
	else
	{
		av_log(NULL, AV_LOG_FATAL, "Open input file  %s success\n",inputUrl.c_str());
	}
	return ret;
}

二.讀取視頻包。注意這里的視頻包是”編碼”以后的數據,這里的編碼不同於很復雜H264算法,只是名義上編碼,

讀取視頻包的代碼如下:

shared_ptr<AVPacket> ReadPacketFromSource()
{
	shared_ptr<AVPacket> packet(static_cast<AVPacket*>(av_malloc(sizeof(AVPacket))), [&](AVPacket *p) { av_packet_free(&p); av_freep(&p);});
	av_init_packet(packet.get());
	lastReadPacktTime = av_gettime();
	int ret = av_read_frame(inputContext, packet.get());
	if(ret >= 0)
	{
		return packet;
	}
	else
	{
		return nullptr;
	}
}

三. 解碼視頻包,首先創建解碼器,接着初始化解碼器,最后是調用解碼Ffmpeg API進行解碼,具體的代碼如下:

int InitDecodeContext(AVStream *inputStream)
{	
	auto codecId = inputStream->codec->codec_id;
	auto codec = avcodec_find_decoder(codecId);
	if (!codec)
	{
		return -1;
	}

	int ret = avcodec_open2(inputStream->codec, codec, NULL);
	return ret;

}

bool Decode(AVStream* inputStream,AVPacket* packet, AVFrame *frame)
{
  int gotFrame = 0;
  auto hr = avcodec_decode_video2(inputStream->codec, frame, &gotFrame, packet);
  if (hr >= 0 && gotFrame != 0)
  {
    return true;
  }
  return false;
}

 四. 編碼 將視頻包解碼后要再編碼的,編碼之前同樣需要初始化編碼器。編碼的格式設置為H264,具體的代碼如下:

int initEncoderCodec(AVStream* inputStream,AVCodecContext **encodeContext)
{
     AVCodec * picCodec;       picCodec = avcodec_find_encoder(AV_CODEC_ID_H264);      (*encodeContext) = avcodec_alloc_context3(picCodec);      (*encodeContext)->codec_id = picCodec->id; (*encodeContext)->has_b_frames = 0; (*encodeContext)->time_base.num = inputStream->codec->time_base.num; (*encodeContext)->time_base.den = inputStream->codec->time_base.den; (*encodeContext)->pix_fmt = *picCodec->pix_fmts; (*encodeContext)->width = inputStream->codec->width; (*encodeContext)->height =inputStream->codec->height; (*encodeContext)->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; int ret = avcodec_open2((*encodeContext), picCodec, nullptr); if (ret < 0) { std::cout<<"open video codec failed"<<endl; return ret; } return 1; } std::shared_ptr<AVPacket> Encode(AVCodecContext *encodeContext,AVFrame * frame) { int gotOutput = 0; std::shared_ptr<AVPacket> pkt(static_cast<AVPacket*>(av_malloc(sizeof(AVPacket))), [&](AVPacket *p) { av_packet_free(&p); av_freep(&p); }); av_init_packet(pkt.get()); pkt->data = NULL; pkt->size = 0; int ret = avcodec_encode_video2(encodeContext, pkt.get(), frame, &gotOutput); if (ret >= 0 && gotOutput) {   return pkt; } else {   return nullptr; } }

  五 . 封裝寫文件,編碼后的視頻數據不能直接寫入文件,需要經過封裝(打包)成mp4格式。首先我們得創建輸出上下文,

並為它指定視頻格式。具體的代碼如下:

int OpenOutput(string outUrl,AVCodecContext *encodeCodec)
{

	int ret  = avformat_alloc_output_context2(&outputContext, nullptr, "mp4", outUrl.c_str());
	if(ret < 0)
	{
		av_log(NULL, AV_LOG_ERROR, "open output context failed\n");
		goto Error;
	}

	ret = avio_open2(&outputContext->pb, outUrl.c_str(), AVIO_FLAG_WRITE,nullptr, nullptr);	
	if(ret < 0)
	{
		av_log(NULL, AV_LOG_ERROR, "open avio failed");
		goto Error;
	}

	for(int i = 0; i < inputContext->nb_streams; i++)
	{
		if(inputContext->streams[i]->codec->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO)
		{
			continue;
		}
		AVStream * stream = avformat_new_stream(outputContext, encodeCodec->codec);				
		ret = avcodec_copy_context(stream->codec, encodeCodec);	
		if(ret < 0)
		{
			av_log(NULL, AV_LOG_ERROR, "copy coddec context failed");
			goto Error;
		}
	}

	ret = avformat_write_header(outputContext, nullptr);
	if(ret < 0)
	{
		av_log(NULL, AV_LOG_ERROR, "format write header failed");
		goto Error;
	}

	av_log(NULL, AV_LOG_FATAL, " Open output file success %s\n",outUrl.c_str());			
	return ret ;
Error:
	if(outputContext)
	{
		for(int i = 0; i < outputContext->nb_streams; i++)
		{
			avcodec_close(outputContext->streams[i]->codec);
		}
		avformat_close_input(&outputContext);
	}
	return ret ;
}

int WritePacket(shared_ptr<AVPacket> packet)
{
	auto inputStream = inputContext->streams[packet->stream_index];
	auto outputStream = outputContext->streams[packet->stream_index];	
	packet->pts = packet->dts = packetCount * (outputContext->streams[0]->time_base.den) / 
		             outputContext->streams[0]->time_base.num / 30 ;
	//cout <<"pts:"<<packet->pts<<endl;
	packetCount++;
	return av_interleaved_write_frame(outputContext, packet.get());
}

 六 .調用實例

int _tmain(int argc, _TCHAR* argv[])
{
	
	SwsScaleContext swsScaleContext;
	AVFrame *videoFrame = av_frame_alloc();
	AVFrame *pSwsVideoFrame = av_frame_alloc();

	Init();
	int ret = OpenInput("video=USB2.0 Camera");
	
	if(ret <0) goto Error;
	
	InitDecodeContext(inputContext->streams[0]);
	

	ret = initEncoderCodec(inputContext->streams[0],&encodeContext);

	if(ret >= 0)
	{
		ret = OpenOutput("D:\\usbCamera.mp4",encodeContext); 
	}
	if(ret <0) goto Error;
	
	
	swsScaleContext.SetSrcResolution(inputContext->streams[0]->codec->width, inputContext->streams[0]->codec->height);

	swsScaleContext.SetDstResolution(encodeContext->width,encodeContext->height);
	swsScaleContext.SetFormat(inputContext->streams[0]->codec->pix_fmt, encodeContext->pix_fmt);
	initSwsContext(&pSwsContext, &swsScaleContext);	
	initSwsFrame(pSwsVideoFrame,encodeContext->width, encodeContext->height);
	int64_t startTime = av_gettime();
	 while(true)
	 {
		auto packet = ReadPacketFromSource();
		if(av_gettime() - startTime > 30 * 1000 * 1000)
		{
			break;
		}
		if(packet && packet->stream_index == 0)
		{
			if(Decode(inputContext->streams[0],packet.get(),videoFrame))
			{
				sws_scale(pSwsContext, (const uint8_t *const *)videoFrame->data,
					videoFrame->linesize, 0, inputContext->streams[0]->codec->height, (uint8_t *const *)pSwsVideoFrame->data, pSwsVideoFrame->linesize);
				auto packetEncode = Encode(encodeContext,pSwsVideoFrame);
				if(packetEncode)
				{
					ret = WritePacket(packetEncode);
					//cout <<"ret:" << ret<<endl;
				}

			}
						
		}
		
	 }
	 cout <<"Get Picture End "<<endl;
	 av_frame_free(&videoFrame);
	 avcodec_close(encodeContext);
	 av_frame_free(&pSwsVideoFrame);
	 
	Error:
	CloseInput();
	CloseOutput();
	
	while(true)
	{
		this_thread::sleep_for(chrono::seconds(100));
	}
	return 0;
}

  

如需交流,可以加QQ群1038388075,766718184,或者QQ:350197870

 視頻教程 播放地址: http://www.iqiyi.com/u/1426749687

視頻下載地址:http://www.chungen90.com/?news_3/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM