ffmpeg本身自己有一个截图的功能,但是只能指定某个帧进行截图,而指定某一帧进行截图完全是盲目的,可能截到的是一张完全黑屏的画面,如果我们能够在视频的帧序列中进行扫描,然后能够自动挑选一些比较理想的图像进行输出,那对于视频网站需要自动生成截图作为视频的封面的需求来说就会比较理想。
下面的程序就是利用ffmpeg实现了上述的需求:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <string>
#include <sys/stat.h>
#include <map>
#define UINT64_C (unsigned long long)
extern "C"
{
#include "libavformat/avformat.h"
#include "libavdevice/avdevice.h"
#include "libswscale/swscale.h"
}
#ifndef INT64_MAX
#define INT64_MAX 9223372036854775807LL
#endif
using namespace std;
#include "imagehandler.h"
// 全局参数声明
string input; // 输入的视频文件路径
string output_prefix; // 输出文件的路径前缀
int output_width = 120; // 输出的图像大小
int output_height = 90; // 输出的图像大小
int sample_gopsize = 25; // 采用的帧间隔
int output_samples = 5; // 输出的截图的数量
int output_quality = 80; // jpeg压缩的质量
bool output_override = false; // 是否可以覆盖已经存在的文件
// 已经产生的截图的得分值的映射表
typedef map<string, double> t_image_score;
t_image_score image_score;
AVCodecContext *avcodec_opts[AVMEDIA_TYPE_NB];
AVFormatContext *avformat_opts;
void init_opts(void)
{
int i;
for (i = 0; i < AVMEDIA_TYPE_NB; i++)
{
avcodec_opts[i] = avcodec_alloc_context2((enum AVMediaType)i);
}
avformat_opts = avformat_alloc_context();
}
void print_help( const char* program_name )
{
fprintf( stderr,
"Usage: %s -i <video file name> -o <file prefix name > -n <expected output thumbnails> -r <resolution w*h> /n"
" -g <frame gop number per sample, dafult is 25>/n"
" -q <compression quality>/n"
" -y <overridable>/n",
program_name );
exit(1);
}
bool parse_opt( int argc, char **argv )
{
// 读取命令行参数
if (argc < 5 )
{
print_help( argv[0] );
}
int i = 1;
while( i < argc )
{
if( strcmp( argv[i], "-i" ) == 0 )
{
i++;
input = argv[i];
}
else if( strcmp( argv[i], "-o" ) == 0 )
{
i++;
output_prefix = argv[i];
}
else if( strcmp( argv[i], "-n" ) == 0 )
{
i++;
output_samples = atoi( argv[i] );
if( output_samples <= 0 )
{
fprintf( stderr, "Expected output thumbnails cannot be less than zero./n" );
return false;
}
}
else if( strcmp( argv[i], "-r" ) == 0 )
{
i++;
string resolution = argv[i];
string::size_type starpos = resolution.find( '*' );
if( starpos == resolution.npos )
{
goto lbl_resolution_error;
}
do
{
string s_width, s_height;
s_width = resolution.substr( 0, starpos );
s_height = resolution.substr( starpos + 1 );
output_width = atoi( s_width.c_str() );
output_height = atoi( s_height.c_str() );
if( output_width <= 0 || output_height <= 0 )
{
if( s_width != "0" || s_height != "0" )
{
goto lbl_resolution_error;
}
output_width = 120;
output_height = 90;
}
}while( false );
goto lblNext;
lbl_resolution_error:
fprintf( stderr, "Resolution is invalid, must be width*height./n" );
return false;
}
else if( strcmp( argv[i], "-g" ) == 0 )
{
i++;
sample_gopsize = atoi( argv[i] );
if( sample_gopsize <= 0 )
{
fprintf( stderr, "Frame gop number per sample cannot be less than zero./n" );
return false;
}
}
else if( strcmp( argv[i], "-q" ) == 0 )
{
i++;
output_quality = atoi( argv[i] );
if( i <= 10 || i > 100 )
{
fprintf( stderr, "quality must between 10 and 100./n" );
return false;
}
}
else if( strcmp( argv[i], "-y" ) == 0 )
{
output_override = true;
}
else if( strcmp( argv[i], "-h" ) == 0
|| strcmp( argv[i], "-?" ) == 0 )
{
print_help( argv[0] );
}
lblNext:
i++;
}
return true;
}
/* open a given stream. Return 0 if OK */
static int open_stream( AVFormatContext* ic, int stream_index )
{
AVCodecContext *avctx;
AVCodec *codec;
if (stream_index < 0 || stream_index >= (int)ic->nb_streams )
{
return -1;
}
avctx = ic->streams[stream_index]->codec;
/* prepare audio output */
if (avctx->codec_type != AVMEDIA_TYPE_VIDEO )
{
return -1;
}
codec = avcodec_find_decoder( avctx->codec_id );
avctx->debug_mv = 0;
avctx->debug = 0;
avctx->workaround_bugs = 1;
avctx->lowres = 0;
if(avctx->lowres)
{
avctx->flags |= CODEC_FLAG_EMU_EDGE;
}
avctx->idct_algo= FF_IDCT_AUTO;
avctx->skip_frame = AVDISCARD_DEFAULT;
avctx->skip_idct = AVDISCARD_DEFAULT;
avctx->skip_loop_filter = AVDISCARD_DEFAULT;
avctx->error_recognition = FF_ER_CAREFUL;
avctx->error_concealment = 3;
avcodec_thread_init(avctx, 1 );
//set_context_opts( avctx, avcodec_opts[avctx->codec_type], 0, codec );
if (!codec ||
avcodec_open(avctx, codec) < 0 )
{
return -1;
}
ic->streams[stream_index]->discard = AVDISCARD_DEFAULT;
return 0;
}
bool is_file_exists( const char* filename )
{
struct stat buff;
int err = stat( filename, &buff );
if (err == -1 )
{
if (errno == ENOENT || errno == ENOTDIR)
{
return false;
}
}
return true;
}
bool rearrange_output( )
{
typedef map<double,string> t_image_score_2;
t_image_score_2 image_score_2;
for( t_image_score::iterator iter = image_score.begin();
iter != image_score.end();
iter++ )
{
image_score_2.insert( make_pair( iter->second, iter->first ) );
}
int counter = 1;
int output_counter = 0;
for( t_image_score_2::iterator iter2 = image_score_2.begin();
iter2 != image_score_2.end();
iter2++, counter++ )
{
char new_file_name[2048];
sprintf( new_file_name, "%s_%03d.jpg", output_prefix.c_str(), counter );
if( is_file_exists( new_file_name ) && !output_override )
{
printf( "Cannot override the existing file %s/n", new_file_name );
continue;
}
// 删除已经存在的文件
unlink( new_file_name );
if( rename( iter2->second.c_str(), new_file_name ) != 0 )
{
printf( "Failed to rename file from %s to %s/n",
iter2->second.c_str(),
new_file_name );
unlink( iter2->second.c_str() );
}
else
{
printf( "<<< New thumbernail <<%s>> generated, score: %.3f./n",
new_file_name,
iter2->first );
output_counter++;
}
}
if( (size_t)output_counter == image_score_2.size() )
{
return true;
}
else
{
return false;
}
}
bool can_cease_now( )
{
if( image_score.size() < output_samples )
{
return false;
}
double max_score = 0.0f;
for( t_image_score::iterator iter = image_score.begin();
iter != image_score.end();
iter++ )
{
if( max_score < iter->second )
{
max_score = iter->second;
}
}
if( max_score < 1000.0f )
{
return true;
}
else
{
return false;
}
}
int main(int argc, char **argv)
{
AVFormatContext *ic = NULL;
int video_index;
int audio_index;
int i;
if( !parse_opt( argc, argv ) )
{
exit( 1 );
}
fprintf( stderr,
"in:%s/nout:%s/nres:%dx%d/ngop:%d/nsamples:%d/n",
input.c_str(),
output_prefix.c_str(),
output_width,
output_height,
sample_gopsize,
output_samples );
avcodec_register_all();
av_register_all();
init_opts( );
ic = avformat_alloc_context();
AVFormatParameters params, *ap = ¶ms;
memset( ap, 0, sizeof(*ap) );
/*
ap->prealloced_context = 1;
ap->width = 0;
ap->height= 0;
ap->time_base= (AVRational){1, 25};
ap->pix_fmt = frame_pix_fmt;
*/
int err = av_open_input_file( &ic,
input.c_str(),
NULL,
0,
NULL );
if( err < 0 )
{
fprintf( stderr,
"Failed to open the input file, %s/n",
input.c_str() );
exit( 1 );
}
fprintf( stderr, "Open file success./n" );
err = av_find_stream_info( ic );
if( err < 0 )
{
fprintf( stderr,
"%s: could not find codec parameters/n",
input.c_str() );
exit( 1 );
}
video_index = -1;
audio_index = -1;
for ( i = 0; i < ic->nb_streams && (video_index < 0 || audio_index < 0); i++ )
{
switch ( ic->streams[i]->codec->codec_type )
{
case CODEC_TYPE_VIDEO:
video_index = i;
break;
case CODEC_TYPE_AUDIO:
audio_index = i;
break;
default:
break;
}
}
printf( "video_index: %d/naudio_index: %d/n", video_index, audio_index );
if( open_stream( ic, video_index ) == -1 )
{
fprintf( stderr,
"Failed to open video stream./n" );
exit( 1 );
}
fprintf( stderr,
"Open video stream ok./n" );
AVCodecContext* avctx = ic->streams[video_index]->codec;
AVPacket pkt1, *pkt = &pkt1;
AVFrame *frame = avcodec_alloc_frame();
AVFrame *frameRGB = avcodec_alloc_frame();
int nFrameCounter = 0;
while( av_read_frame( ic, pkt ) >= 0 )
{
if( pkt->stream_index != video_index )
{
continue;
}
int got_picture = 0;
avcodec_decode_video2( avctx,
frame,
&got_picture,
pkt );
if( frame->linesize[0] == 0 && frame->linesize[1] == 0 && frame->linesize[2] == 0 )
{
continue;
}
// 如果没有产生足够的截图,则先尽快产生
// 如果已经产生足够的截图,则根据指定的sample的间隔数来产生截图
nFrameCounter++;
if( image_score.size() >= (size_t)output_samples
&& sample_gopsize > 0 && (nFrameCounter % sample_gopsize != 0) )
//|| (pkt->flags & PKT_FLAG_KEY) == 0 )
{
continue;
}
// 对图像先进性缩放处理,让后通过去边的方式,产生与需求一致大小的输出图像
// 图像转换过程中的中间状态的宽和高
int width_tmp, height_tmp;
if( avctx->width * output_height < avctx->height * output_width )
{
width_tmp = output_width;
height_tmp = avctx->height * output_width / avctx->width;
}
else
{
width_tmp = avctx->width * output_height / avctx->height;
height_tmp = output_height;
}
int numBytes = ((width_tmp * 3 + 3) & 0xFFFFFFFC) * height_tmp;
//avpicture_get_size( PIX_FMT_RGB24,
// width_tmp,
// height_tmp );
unsigned char* buffer = (unsigned char*)malloc( numBytes );
avpicture_fill((AVPicture *)frameRGB,
buffer,
PIX_FMT_RGB24,
width_tmp,
height_tmp );
frameRGB->linesize[0] = (width_tmp * 3 + 3 ) & 0xFFFFFFFC;
// other codes
struct SwsContext*
img_convert_ctx = sws_getContext( avctx->width,
avctx->height,
avctx->pix_fmt,
width_tmp,
height_tmp,
PIX_FMT_RGB24,
SWS_BICUBIC,
NULL,
NULL,
NULL );
// other codes
// Convert the image from its native format to RGB
sws_scale( img_convert_ctx,
(const uint8_t* const*)frame->data,
frame->linesize,
0,
avctx->height,
frameRGB->data,
frameRGB->linesize );
int clipleft = (width_tmp - output_width) >> 1;
int cliptop = (height_tmp - output_height) >> 1;
image_clip( (const char*)frameRGB->data[0],
width_tmp,
height_tmp,
(char*)frameRGB->data[0],
clipleft,
cliptop,
output_width,
output_height );
// 计算图像RGB的的分值,得分越小,说明色彩越丰富,越需要优先输出
double score = calc_image_score( (char*)frameRGB->data[0],
output_width,
output_height );
// 获取本次需要生成的截图文件名
string output_filename = output_prefix;
t_image_score::iterator iterMax = image_score.end();
if( image_score.size() < (size_t)output_samples )
{
char tmp[2048];
sprintf( tmp, "_%03d.jpg.tmp", (int)(image_score.size()+1) );
output_filename += tmp;
}
else
{
double score_tmp = score;
for( t_image_score::iterator iter = image_score.begin();
iter != image_score.end();
iter++ )
{
if( iter->second > score_tmp )
{
score_tmp = iter->second;
iterMax = iter;
}
}
if( score_tmp != score )
{
output_filename = iterMax->first;
}
else
{
output_filename.clear( );
}
}
if( !output_filename.empty() )
{
int outputlinesize = (output_width * 3 + 3 ) & 0xFFFFFFFC;
if( save_jpeg( output_filename.c_str(),
(const char*)frameRGB->data[0],
output_width,
output_height,
outputlinesize,
output_quality,
true ) )
{
if( image_score.size() < (size_t)output_samples )
{
printf( "New Thumbnail was saved to %s successfully, score:%.3f./n", output_filename.c_str(), score );
image_score.insert( make_pair( output_filename , score ) );
}
else
{
printf( "Thumbnail was saved to %s successfully, score:%.3f./n", output_filename.c_str(), score );
iterMax->second = score;
}
}
}
free( buffer );
sws_freeContext( img_convert_ctx );
img_convert_ctx = NULL;
if( can_cease_now( ) )
{
break;
}
}
av_free( frame );
av_free( frameRGB );
av_free_packet( pkt );
if( rearrange_output( ) )
{
return 0;
}
else
{
return 1;
}
}