现在的位置: 首页 > 综合 > 正文

使用opencv开发视频播放器一

2017年12月12日 ⁄ 综合 ⁄ 共 6917字 ⁄ 字号 评论关闭

最近由于项目需求,要在界面中内嵌一个简单的视频播放器,能够打开视频,逐帧播放,进度条拖动等功能。

因此,首先尝试使用opencv编写。原因:1、便于后续处理;2、opencv提高的接口较完善。很快就动手写出了一个;基于OpenCV的视频播放器。

#include "stdafx.h"
#include <opencv2/opencv.hpp>
#include <time.h>
#include <Windows.h>
#include <iostream>
#include <stdlib.h>

using namespace std;
using namespace cv;

int g_slider_position = 0;
int n_position = 0;
VideoCapture pCap;  


// 回调函数  
void onTrackbarSlide( int pos, void * )  
{
	if (pos != n_position)
	{
		pCap.set(CV_CAP_PROP_POS_FRAMES, pos); 
		n_position = pos;
		g_slider_position = pos;
	}
}
int main( int argc, char *argv[] )
{	
	Mat pFrame; 
	int nFrames = 0;
	double fps = 0; // 帧率  
	int spf = 0;    // 1/fps

	//char filename[200] = "768x576.avi";

	if (argc == 2)  
	{  
		sprintf(filename, "%s", argv[1]);
	}

	namedWindow("VideoPlayer", 0);    // 可改变窗口大小
	pCap.open(filename);

	if (!pCap.isOpened())  
	{  
		printf("Count not open video file/n");  
		exit(1);  
	}  

	// 获取视频帧率  
	fps = pCap.get(CV_CAP_PROP_FPS);  
	spf = (int)(1000/fps);  
	printf("fps = %.f\nspf = %d\n", fps, spf);  
	// 获取视频总帧数 
	nFrames = (int)(pCap.get(CV_CAP_PROP_FRAME_COUNT));
	printf("Total frames = %d\n", nFrames);
	// 如果由于编码方式问题获取不到帧数,则不创建滚动条  
	if (nFrames != 0)  
	{  
		createTrackbar(
		"Position", 
		"VideoPlayer", 
		&g_slider_position,   
		nFrames, 
		onTrackbarSlide
		);  
	}  

	while (1)  
	{
		pCap >> pFrame;

		if (pFrame.empty())        // 播放结束退出  
		{  
			break;  
		}  

		imshow("VideoPlayer", pFrame);  
		// 播放的过程中移动滚动条
		printf("current pos %d\n", n_position);
		n_position++;
		g_slider_position = n_position;

		setTrackbarPos("Position", "VideoPlayer", g_slider_position);

		char c = waitKey((int)(spf)); // 按原来的帧率播放视频  
		if (c == 27)        // 按下Esc退出播放  
		{  
			break;  
		}  
	}  
	pCap.release();
	pFrame.release();
	destroyWindow("VideoPlayer");
	destroyWindow("VideoPlayerControl");
	return 0;
}

在播放部分avi格式的视频时,拖动进度条会出现问题帧定位不准的问题,可能是定位到了关键帧。

google了一番,大家认为是从opencv2.x开始,开始使用ffmpeg。ffmpeg是一个开源的视频库,能够打开视频,转码等等。KMPlayer等等播放器都使用过这个库,只是因为违反了LGPL,被ffmpeg社区拉黑了。

下面给出的是X:\OpenCV\OpenCV231\modules\highgui\src\cap_ffmpeg_impl.hpp


bool CvCapture_FFMPEG::open( const char* _filename )
{
    unsigned i;
    bool valid = false;

    close();

    /* register all codecs, demux and protocols */
    av_register_all();

#ifndef _DEBUG
    // av_log_level = AV_LOG_QUIET;
#endif

    int err = av_open_input_file(&ic, _filename, NULL, 0, NULL);
    if (err < 0) {
	    CV_WARN("Error opening file");
	    goto exit_func;
    }
    err = av_find_stream_info(ic);
    if (err < 0) {
	    CV_WARN("Could not find codec parameters");
	    goto exit_func;
    }
    for(i = 0; i < ic->nb_streams; i++) {
#if LIBAVFORMAT_BUILD > 4628
        AVCodecContext *enc = ic->streams[i]->codec;
#else
        AVCodecContext *enc = &ic->streams[i]->codec;
#endif

        avcodec_thread_init(enc, get_number_of_cpus());

        #if LIBAVFORMAT_BUILD < CALC_FFMPEG_VERSION(53, 4, 0)
            #define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
        #endif
        
        if( AVMEDIA_TYPE_VIDEO == enc->codec_type && video_stream < 0) {
            AVCodec *codec = avcodec_find_decoder(enc->codec_id);
            if (!codec ||
            avcodec_open(enc, codec) < 0)
            goto exit_func;
            video_stream = i;
            video_st = ic->streams[i];
            picture = avcodec_alloc_frame();

            rgb_picture.data[0] = (uint8_t*)malloc(
                                    avpicture_get_size( PIX_FMT_BGR24,
                                    enc->width, enc->height ));
            avpicture_fill( (AVPicture*)&rgb_picture, rgb_picture.data[0],
                    PIX_FMT_BGR24, enc->width, enc->height );

            frame.width = enc->width;
            frame.height = enc->height;
            frame.cn = 3;
            frame.step = rgb_picture.linesize[0];
            frame.data = rgb_picture.data[0];
            break;
        }
    }

    if(video_stream >= 0) valid = true;

    // perform check if source is seekable via ffmpeg's seek function av_seek_frame(...)
    err = av_seek_frame(ic, video_stream, 10, 0);
    if (err < 0)
    {
        filename=(char*)malloc(strlen(_filename)+1);
        strcpy(filename, _filename);
        // reopen videofile to 'seek' back to first frame
        reopen();
    }
    else
    {
        // seek seems to work, so we don't need the filename,
        // but we still need to seek back to filestart
        filename=NULL;
        int64_t ts    = video_st->first_dts;
        int     flags = AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD;
        av_seek_frame(ic, video_stream, ts, flags);
    }
exit_func:

    if( !valid )
        close();

    return valid;
}

err = av_seek_frame(ic, video_stream, 10, 0);

是大家认为opencv只能定位关键帧的原因所在。因此,大家推荐的做法是将0改成AVSEEK_FLAG_ANY,然后重新编译opencv。

第二个问题是,opencv能够打开并播放mp4,ts等格式的视频文件,但是不能提供帧定位。只有拖动进度条重新定位,视频就会从头开始播放。面对这个问题甚是苦恼。


既然opencv使用的是ffmpeg,那么我可以直接用ffmpeg的库进行视频文件的操作。因此从www.ffmpeg.org下载最新的static、shared和dev文件,解压出dev和shared,将shared中的bin文件夹拷贝到dev中。(我只是为了用起来方便,请原谅我的轻度强迫症)

我把dev放在了c盘根目录下,命名为ffmpeg。将c:\ffmpeg\bin加入到环境变量的path。然后注销一下。


在工程中加入ffmpeg\include和ffmpeg\lib,并将ffmpeg\lib中的*.lib加入到链接库中。在代码中加入对应的头文件就可以了。


#include "stdafx.h"
#include <opencv2/opencv.hpp>
#include <time.h>
#include <Windows.h>
#include <iostream>
#include <stdlib.h>
#include <math.h>

#ifdef __cplusplus
extern "C" {
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/imgutils.h>
#include <libavutil/mathematics.h>
#include <libavutil/samplefmt.h>
#ifdef __cplusplus
}
#endif

#define N 3

#define INBUF_SIZE 4096
#define AUDIO_INBUF_SIZE 20480
#define AUDIO_REFILL_THRESH 4096

using namespace std;
using namespace cv;

int main( int argc, char *argv[] )  
{
	AVFormatContext *pFormatCtx;
	int codec_id = CODEC_ID_MPEG4;
	char filename[] = "Locust_Inspired_Jumping_Robot.flv";
	// Open video file
	AVCodec *codec;
	AVCodecContext *c= NULL;
	int i, out_size, x, y, outbuf_size;
	FILE *f;
	AVFrame *picture;
	uint8_t *outbuf;
	int had_output = 0;

	av_register_all();
	printf("Encode video file %s\n", filename);

	codec = avcodec_find_encoder(CODEC_ID_H264);
	if (!codec) 
	{
		fprintf(stderr, "codec not found\n");
		exit(1);
	}

	c = avcodec_alloc_context3(codec);
	picture = avcodec_alloc_frame();

	/* put sample parameters */
	c->bit_rate = 40000;
	//c->bit_rate_tolerance=30;
	/* resolution must be a multiple of two */
	c->width = 352;
	c->height = 288;
	/* frames per second */

	c->time_base.den=  25;
	c->time_base.num= 1;
	c->gop_size = 10; /* emit one intra frame every ten frames */
	c->max_b_frames=1;
	c->pix_fmt = PIX_FMT_YUV420P;

	if(codec_id == CODEC_ID_H264)
	av_opt_set(c->priv_data, "preset", "slow", 0);
	/* open it */
	if (avcodec_open2(c, codec, NULL) < 0) 
	{
		fprintf(stderr, "could not open codec\n");
		exit(1);
	}

	f = fopen(filename, "wb");
	if (!f) 
	{
		fprintf(stderr, "could not open %s\n", filename);
		exit(1);
	}

	/* alloc image and output buffer */
	outbuf_size = 100000 + 12*c->width*c->height;
	outbuf = (uint8_t*)malloc(outbuf_size); //CHANGED

	/* the image can be allocated by any means and av_image_alloc() is
	* just the most convenient way if av_malloc() is to be used */
	av_image_alloc(picture->data, picture->linesize, c->width, c->height, c->pix_fmt, 1);

	/* encode 1 second of video */
	for(i = 0; i < 25; i++) 
	{
		fflush(stdout);
		/* prepare a dummy image */
		/* Y */
		for(y = 0; y < c->height; y++) 
		{
			for(x = 0; x < c->width; x++) 
			{
				picture->data[0][y * picture->linesize[0] + x] = x + y + i * 3;
			}
		}

		/* Cb and Cr */
		for(y = 0; y < c->height / 2; y++) 
		{
			for(x = 0; x < c->width / 2; x++) 
			{
				picture->data[1][y * picture->linesize[1] + x] = 128 + y + i * 2;
				picture->data[2][y * picture->linesize[2] + x] = 64 + x + i * 5;
			}
		}

		/* encode the image */
		out_size = avcodec_encode_video(c, outbuf, outbuf_size, picture);
		had_output |= out_size;
		printf("encoding frame %3d (size=%5d)\n", i, out_size);
		fwrite(outbuf, 1, out_size, f);
	}

	/* get the delayed frames */
	for(; out_size || !had_output; i++) 
	{
		fflush(stdout);

		out_size = avcodec_encode_video(c, outbuf, outbuf_size, NULL);
		had_output |= out_size;
		printf("write frame %3d (size=%5d)\n", i, out_size);
		fwrite(outbuf, 1, out_size, f);
	}

	/* add sequence end code to have a real mpeg file */
	outbuf[0] = 0x00;
	outbuf[1] = 0x00;
	outbuf[2] = 0x01;
	outbuf[3] = 0xb7;
	fwrite(outbuf, 1, 4, f);
	fclose(f);
	free(outbuf);

	avcodec_close(c);
	av_free(c);
	av_free(picture->data[0]);
	av_free(picture);
	printf("\n");

	return 0;
}

值得注意的是,在包含ffmpeg的头文件时一定要定义在extern “C”中,同时这些头文件必须在#include <math.h>之后。


然后编译一下,就可以运行了。目前做到这一步了,现挖个坑,等把如何用ffmpeg读取编码输出视频的问题解决了再继续写。


未完待续

抱歉!评论已关闭.