使用ffmpeg+SDL的简单播放器

使用ffmpeg+SDL的简单播放器,做了简单同步,还有许多问题,谨慎参考,直接上代码:

#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include <stdio.h>
#include "SDL/SDL.h"
#include "SDL_thread.h"

#define PICTURE_W 720
#define PICTURE_H 576
#define PICTURE_PW 720
#define PICTURE_PH 576

#define MAX_QUEUE_SIZE 100
#define VIDEO_PICTURE_QUEUE_SIZE 2
typedef struct VideoPicture_t{
	SDL_Overlay *bmp;
	double pts;
	double duration;
	int width,height;
}VideoPicture;


struct play_info_t{
	double audio_clock;//解码时钟
	double audio_current_pts;//当前音频的pts
	double vedio_clock;//解码时钟
	double last_frame_pts;//上一帧的PTS值
	double last_frame_delay;//上一帧的延时
	double frame_timer;
	
	AVCodecContext *pVideoCodecCtx;
	AVCodecContext *pAudioCodecCtx;

	VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];
	int pictq_size, pictq_rindex, pictq_windex;
    SDL_mutex *pictq_mutex;
    SDL_cond *pictq_cond;

	struct SwrContext *audio_conv;
	struct SwsContext *video_conv;
};

struct play_info_t g_play_info;




typedef struct PacketQueue {
	AVPacketList *first_pkt, *last_pkt;
	int nb_packets;
	int size;
	SDL_mutex *mutex;
	SDL_cond *cond;
} PacketQueue;

PacketQueue audio_queue;
PacketQueue video_queue;
int quit = 0;
SDL_Surface *screen;
SDL_Overlay *bmp;
SDL_Rect rect;

int global_video_pkt_pts=0;
int our_get_buffer(struct AVCodecContext *c, AVFrame *pic) {
	int ret = avcodec_default_get_buffer(c, pic);
	uint64_t *pts = av_malloc(sizeof(uint64_t));
	*pts = global_video_pkt_pts;
	pic->opaque = pts;
	return ret;
}
void our_release_buffer(struct AVCodecContext *c, AVFrame *pic) {
	if(pic) av_freep(&pic->opaque);
	avcodec_default_release_buffer(c, pic);
}

double get_audio_clock(void)
{
	return g_play_info.audio_current_pts;
}

double get_video_clock(void)
{
	return g_play_info.vedio_clock;
}

void picture_queque_init(struct play_info_t *play)
{
	int i;
	play->pictq_cond=SDL_CreateCond();
	play->pictq_mutex=SDL_CreateMutex();
	play->pictq_rindex=play->pictq_windex=0;
	play->pictq_size=0;
	memset(play->pictq,0,sizeof(play->pictq));
	for(i=0;i<VIDEO_PICTURE_QUEUE_SIZE;i++){
		play->pictq[i].bmp=SDL_CreateYUVOverlay(PICTURE_W,PICTURE_H,SDL_YUY2_OVERLAY,screen);
	}
}

void picture_queque_destroy(struct play_info_t *play)
{
	int i;
	for(i=0;i<VIDEO_PICTURE_QUEUE_SIZE;i++){
		SDL_FreeYUVOverlay(play->pictq[i].bmp);
	}
}



void packet_queue_init(PacketQueue *q) {
	memset(q, 0, sizeof(PacketQueue));
	q->mutex = SDL_CreateMutex();
	q->cond = SDL_CreateCond();
}

int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
	AVPacketList *pkt1;
	if(av_dup_packet(pkt) < 0) {
		return -1;
	}
	pkt1 = av_malloc(sizeof(AVPacketList));
	if (!pkt1)
		return -1;
	pkt1->pkt = *pkt;
	pkt1->next = NULL;
	SDL_LockMutex(q->mutex);
	if (!q->last_pkt)
		q->first_pkt = pkt1;
	else
		q->last_pkt->next = pkt1;
	q->last_pkt = pkt1;
	q->nb_packets++;
	q->size += pkt1->pkt.size;
	SDL_CondSignal(q->cond);
	SDL_UnlockMutex(q->mutex);
	return 0;
}


static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) {
	AVPacketList *pkt1;
	int ret;
	SDL_LockMutex(q->mutex);
	for(;;) {
		if(quit) {
			ret = -1;
			break;
		}
		pkt1 = q->first_pkt;
		if (pkt1) {
			q->first_pkt = pkt1->next;
			if (!q->first_pkt)
				q->last_pkt = NULL;
			q->nb_packets--;
			q->size -= pkt1->pkt.size;
			*pkt = pkt1->pkt;
			av_free(pkt1);
			ret = 1;
			break;
		} else if (!block) {
			ret = 0;
			break;
		} else {
			SDL_CondWait(q->cond, q->mutex);
		}
	}
	SDL_UnlockMutex(q->mutex);
	return ret;
}

int decode_interrupt_cb(void) {
	return quit;
}

//计算正确的pts值
double sync_video(struct play_info_t *play,AVFrame* frame,double pts)
{
	double frame_delay;
	if(pts!=0)
		play->vedio_clock=pts;
	else
		pts=play->vedio_clock;
	frame_delay=av_q2d(play->pVideoCodecCtx->time_base);//一帧占用的时间
	frame_delay+=frame->repeat_pict*(frame_delay*0.5);//计算重复帧
	play->vedio_clock+=frame_delay;
	return pts;
}

int audio_decode_frame(struct play_info_t* play, uint8_t *audio_buf,int buf_size) {
	AVCodecContext *aCodecCtx=play->pAudioCodecCtx;
	AVFrame *pAudioFrame=avcodec_alloc_frame();
	AVPacket pkt,pkt1;
	int frame_finished=0;
	int pkt_pos,pkt_len;
	int src_len=0,dst_len=0,data_size=0;
	float pts=0;
	avcodec_get_frame_defaults(pAudioFrame);

	uint8_t *out[]={audio_buf};
	
	for(;!quit;){
		if(packet_queue_get(&audio_queue, &pkt, 1) < 0) {
			av_free(pAudioFrame);
			return -1;
		}
		pkt1=pkt;
		pkt_pos=0;
		pkt_len=pkt.size;
		
		while(pkt_pos<pkt.size && !quit){
			if((src_len=avcodec_decode_audio4(aCodecCtx,pAudioFrame,&frame_finished,&pkt1))<0){
				av_free_packet(&pkt);
				av_free(pAudioFrame);
				return -1;
			}
			
			play->audio_clock+=(double)(pAudioFrame->linesize[0])/
				(aCodecCtx->channels*aCodecCtx->sample_rate*av_get_bytes_per_sample(aCodecCtx->sample_fmt));
			
			if(frame_finished){
				int len=swr_convert(play->audio_conv,out,buf_size/aCodecCtx->channels/av_get_bytes_per_sample(AV_SAMPLE_FMT_S16),
					pAudioFrame->data,pAudioFrame->linesize[0]/aCodecCtx->channels/av_get_bytes_per_sample(pAudioFrame->format));
				len=len*aCodecCtx->channels*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
				av_free(pAudioFrame);
				av_free_packet(&pkt);
				return len;
			}else{
				if (!pkt1.data && aCodecCtx->codec->capabilities & CODEC_CAP_DELAY){
					break;
				}
			}
			pkt_pos+=src_len;//已经解码的长度
			pkt1.data=pkt.data+pkt_pos;
			pkt1.size=pkt.size-pkt_pos;
		}
		av_free_packet(&pkt);
	}
	av_free(pAudioFrame);
	return dst_len;
}

//decode the audio data ,and copy the result to stream
void SDLCALL audio_callback(void *userdata, Uint8 *stream, int len)
{
	struct play_info_t *play=(struct play_info_t*)userdata;
	AVCodecContext *aCodecCtx = play->pAudioCodecCtx;
	int len1, audio_size;
	static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
	static unsigned int audio_buf_size = 0;
	static unsigned int audio_buf_index = 0;
	int bytes_per_sec=2*aCodecCtx->channels*aCodecCtx->sample_rate;
	
	while(len > 0 && !quit) {
		if(audio_buf_index >= audio_buf_size) {
			audio_size = audio_decode_frame(play, audio_buf,sizeof(audio_buf));
			if(audio_size < 0) {
				audio_buf_size = 1024;
				memset(audio_buf, 0, audio_buf_size);
			} else {
				audio_buf_size = audio_size;
			}
			audio_buf_index = 0;
		}
		len1 = audio_buf_size - audio_buf_index;
		
		if(len1 > len)
			len1 = len;
		memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
		len -= len1;
		stream += len1;
		audio_buf_index += len1;
	}
	if(audio_buf_size > audio_buf_index){
		play->audio_current_pts=play->audio_clock-((double)(audio_buf_size-audio_buf_index)/(double)bytes_per_sec);
		if(play->audio_current_pts<0)
			play->audio_current_pts=play->audio_clock;
		//printf("audio-pts:%f\n",play->audio_current_pts);
	}
}

int SDLCALL video_decode_callback(void* arg)
{
	printf("---in video decode thread ---\n");
	struct play_info_t* play=(struct play_info_t*)arg;
	AVCodecContext *pVideoCodecCtx=play->pVideoCodecCtx;
	double pts;
	VideoPicture *vp;
	AVPacket pkt;
	int frame_finished;
	AVPicture pict = { { 0 } };
	AVFrame *pVideoFrame=avcodec_alloc_frame();
	if(pVideoFrame==NULL){
		printf("can't alloc video frame!\n");
		return -1;
	}

	while(!quit){
		if(packet_queue_get(&video_queue, &pkt, 1) < 0) {
			return -1;
		}
		pts = 0;
		// Save global pts to be stored in pFrame in first call
		global_video_pkt_pts = pkt.pts;
		avcodec_decode_video2(pVideoCodecCtx,pVideoFrame,&frame_finished,&pkt);
		if(pkt.dts == AV_NOPTS_VALUE && pVideoFrame->opaque && *(uint64_t*)pVideoFrame->opaque != AV_NOPTS_VALUE){
			pts = *(uint64_t *)pVideoFrame->opaque;
		} else if(pkt.dts!=AV_NOPTS_VALUE){
			pts=pkt.dts;
		}else{
			pts=0;
		}
		pts*=av_q2d(pVideoCodecCtx->time_base);
	
		av_free_packet(&pkt);
		if(frame_finished){
			SDL_LockMutex(play->pictq_mutex);
			while(play->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE && !quit){
				SDL_CondWait(play->pictq_cond,play->pictq_mutex);
			}
			SDL_UnlockMutex(play->pictq_mutex);
			if(quit)
				goto out;
			
			vp=&play->pictq[play->pictq_windex];
			SDL_Overlay *bmp=vp->bmp;
			//序列化帧
			pts=sync_video(&g_play_info,pVideoFrame,pts);
			vp->pts=pts;
			//printf("video-pts:%f\n",pts);
			
			SDL_LockYUVOverlay(bmp);
			pict.data[0]=bmp->pixels[0];
			pict.data[1]=bmp->pixels[2];
			pict.data[2]=bmp->pixels[1];
			pict.linesize[0]=bmp->pitches[0];
			pict.linesize[1]=bmp->pitches[2];
			pict.linesize[2]=bmp->pitches[1];
			sws_scale(play->video_conv,pVideoFrame->data,pVideoFrame->linesize,0,
				pVideoFrame->height,pict.data,pict.linesize);
			SDL_UnlockYUVOverlay(bmp);


			play->pictq_windex=(play->pictq_windex+1)%VIDEO_PICTURE_QUEUE_SIZE;
			SDL_LockMutex(play->pictq_mutex);
			play->pictq_size++;
			SDL_UnlockMutex(play->pictq_mutex);
			
		}
	}
out:
	av_free(pVideoFrame);
	printf("---- exit video_decode_callback ----\n");
	return 0;
}

void video_image_display(struct play_info_t *play)
{
	double delay,time;
	VideoPicture *vp;
	double diff,sync_threshold;
	if(play->pictq_size>0){
		vp=&play->pictq[play->pictq_rindex];
		delay=vp->pts - play->last_frame_pts;
		if(delay>0 && delay<10){
			play->last_frame_delay=delay;
		}
		delay=play->last_frame_delay;
		
		diff=vp->pts-get_audio_clock();
		sync_threshold = FFMAX(0.01, delay);
		if (fabs(diff) < 10) {
            if (diff <= -sync_threshold)
                delay = 0;
            else if (diff >= sync_threshold)
                delay = 2 * delay;
        }

		time=av_gettime()/1000000.0;
		if(time < play->frame_timer+delay)
			return ;
		
		if(delay>0)
			play->frame_timer+=delay;
		play->last_frame_pts=vp->pts;

		SDL_DisplayYUVOverlay(vp->bmp,&rect);

		play->pictq_rindex=(play->pictq_rindex+1)%VIDEO_PICTURE_QUEUE_SIZE;

		SDL_LockMutex(play->pictq_mutex);
		play->pictq_size--;
		SDL_CondSignal(play->pictq_cond);
		SDL_UnlockMutex(play->pictq_mutex);
		
	}
}

int SDLCALL video_show_callback(void* arg)
{
	double delay=5000;
	struct play_info_t *play=(struct play_info_t*)arg;
	while(!quit){
		video_image_display(play);
		usleep(delay);
	}
	printf("----exit video_show_callback ----\n");
	return 0;
}

void player_log_callback(void* ptr, int level, const char* fmt, va_list vl)
{
    static int print_prefix = 0;
    static int count;
    char line[1024];
    av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix);
    printf("%s",line);
}




int main(int argc,char *argv[])
{
	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)<0){
		printf("Init SDL err!\n");
		return -1;
	}

	av_log_set_callback(player_log_callback);
	av_register_all();

	AVFormatContext *pFormatCtx;
	pFormatCtx=avformat_alloc_context();
	if(avformat_open_input(&pFormatCtx,argv[1],NULL,NULL)<0){
		printf("avformat_open_input err!\n");
		return -1;
	}

	if(avformat_find_stream_info(pFormatCtx,NULL)<0){
		printf("avformat_find_stream_info err!\n");
		return -1;
	}

	//av_dump_format(pFormatCtx,0,argv[1],0);
	
	int video_stream=-1,audio_stream=-1,i;
	for(i=0;i<pFormatCtx->nb_streams;i++){
		if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
			video_stream=i;
		}
		if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO){
			audio_stream=i;
		}
	}
	if(video_stream==-1 || audio_stream==-1){
		printf("not find video or audio stream!\n");
		avformat_close_input(&pFormatCtx);
		return -1;
	}
	g_play_info.pVideoCodecCtx=pFormatCtx->streams[video_stream]->codec;
	g_play_info.pAudioCodecCtx=pFormatCtx->streams[audio_stream]->codec;

	//find the decoder
	AVCodec *pVideoCodec=avcodec_find_decoder(g_play_info.pVideoCodecCtx->codec_id);
	if(pVideoCodec==NULL){
		printf("not find video decoder!\n");
		avformat_close_input(&pFormatCtx);
		return -1;
	}
	AVCodec *pAudioCodec=avcodec_find_decoder(g_play_info.pAudioCodecCtx->codec_id);
	if(pVideoCodec==NULL){
		printf("not find audio decoder!\n");
		avformat_close_input(&pFormatCtx);
		return -1;
	}
	//open the codec
	if(avcodec_open(g_play_info.pVideoCodecCtx,pVideoCodec)<0){
		printf("can't open the video decoder!\n");
		avformat_close_input(&pFormatCtx);
		return -1;
	}
	if(avcodec_open(g_play_info.pAudioCodecCtx,pAudioCodec)<0){
		printf("can't open the audio decoder!\n");
		avcodec_close(g_play_info.pVideoCodecCtx);
		avformat_close_input(&pFormatCtx);
		return -1;
	}

	//setup SDL
	screen=SDL_SetVideoMode(PICTURE_W,PICTURE_H,0,0);
	picture_queque_init(&g_play_info);
	packet_queue_init(&video_queue);
	packet_queue_init(&audio_queue);
	
	rect.x=0;
	rect.y=0;
	rect.w=PICTURE_W;
	rect.h=PICTURE_H;


	//setup the sdl audio
	SDL_AudioSpec sdl_audio;
	int64_t wanted_channel_layout = 0;
    int wanted_nb_channels;
	wanted_channel_layout = 
		(g_play_info.pAudioCodecCtx->channel_layout && 
		g_play_info.pAudioCodecCtx->channels == av_get_channel_layout_nb_channels(g_play_info.pAudioCodecCtx->channel_layout)) ? g_play_info.pAudioCodecCtx->channel_layout : av_get_default_channel_layout(g_play_info.pAudioCodecCtx->channels);
    wanted_channel_layout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX;
    wanted_nb_channels = av_get_channel_layout_nb_channels(wanted_channel_layout);
    /* SDL only supports 1, 2, 4 or 6 channels at the moment, so we have to make sure not to request anything else. */
    while (wanted_nb_channels > 0 && (wanted_nb_channels == 3 || wanted_nb_channels == 5 || wanted_nb_channels > 6)) {
        wanted_nb_channels--;
        wanted_channel_layout = av_get_default_channel_layout(wanted_nb_channels);
    }
	sdl_audio.channels=av_get_channel_layout_nb_channels(wanted_channel_layout);
	g_play_info.pAudioCodecCtx->channels=sdl_audio.channels;
	g_play_info.pAudioCodecCtx->channel_layout=wanted_channel_layout;
	
	g_play_info.audio_conv=swr_alloc_set_opts(NULL,wanted_channel_layout,AV_SAMPLE_FMT_S16,g_play_info.pAudioCodecCtx->sample_rate,
		wanted_channel_layout,g_play_info.pAudioCodecCtx->sample_fmt,
		g_play_info.pAudioCodecCtx->sample_rate,0,NULL);
	swr_init(g_play_info.audio_conv);
	
	sdl_audio.freq=g_play_info.pAudioCodecCtx->sample_rate;
	sdl_audio.format=AUDIO_S16SYS;
	sdl_audio.channels=g_play_info.pAudioCodecCtx->channels;
	sdl_audio.silence=0;
	sdl_audio.samples=1024;
	sdl_audio.callback=audio_callback;//calls when the audio device need more data
	sdl_audio.userdata=&g_play_info;
	if(SDL_OpenAudio(&sdl_audio,NULL)<0){
		printf("can't open audio device!\n");
		goto err_alloc_frame1;
	}

	g_play_info.vedio_clock=0;
	g_play_info.audio_clock=0;
	g_play_info.audio_current_pts=0;
	g_play_info.last_frame_pts=0;
	g_play_info.last_frame_delay=0;
	g_play_info.frame_timer=av_gettime()/1000000.0;
	g_play_info.pVideoCodecCtx->get_buffer=our_get_buffer;
	g_play_info.pVideoCodecCtx->release_buffer=our_release_buffer;
	
	SDL_PauseAudio(0);

	//create video decode thread
		//setup the converter
	
	g_play_info.video_conv=sws_getContext(g_play_info.pVideoCodecCtx->width,g_play_info.pVideoCodecCtx->height,
		g_play_info.pVideoCodecCtx->pix_fmt,
		PICTURE_PW,PICTURE_PH,PIX_FMT_YUYV422,SWS_POINT,NULL,NULL,NULL);
	SDL_Thread * video_decode_thread=SDL_CreateThread(video_decode_callback,(void*)&g_play_info);
	SDL_Thread * video_show_thread=SDL_CreateThread(video_show_callback,(void*)&g_play_info);
	//start to decode
	SDL_Event sdl_event;
	AVPacket packet;


	
	while(!quit){
		SDL_PollEvent(&sdl_event);
		if(sdl_event.type==SDL_QUIT){
			SDL_CondSignal(video_queue.cond);
			SDL_CondSignal(audio_queue.cond);
			quit=1;
			break;
		}
		if(video_queue.nb_packets>=MAX_QUEUE_SIZE || audio_queue.nb_packets>=MAX_QUEUE_SIZE){
			//printf("%d---%d\n",video_queue.nb_packets,audio_queue.nb_packets);
			usleep(10000);
			continue;
		}
		if(av_read_frame(pFormatCtx,&packet)<0){
			quit=1;
			continue;
		}
		if(packet.stream_index==video_stream){
			packet_queue_put(&video_queue,&packet);
		}else if(packet.stream_index==audio_stream){
			packet_queue_put(&audio_queue,&packet);
		}else{
			av_free_packet(&packet);
		}
		
		//usleep(5000);
	}
	SDL_CloseAudio();
	SDL_CondSignal(g_play_info.pictq_cond);
	SDL_WaitThread(video_decode_thread,NULL);
	SDL_WaitThread(video_show_thread,NULL);
	picture_queque_destroy(&g_play_info);

	swr_free(&g_play_info.audio_conv);
	sws_freeContext(g_play_info.video_conv);

	SDL_FreeSurface(screen);
	avcodec_close(g_play_info.pVideoCodecCtx);
	avcodec_close(g_play_info.pAudioCodecCtx);
	avformat_close_input(&pFormatCtx);
	return 0;

err_alloc_frame1:
	avcodec_close(g_play_info.pAudioCodecCtx);
	avcodec_close(g_play_info.pVideoCodecCtx);
	avformat_close_input(&pFormatCtx);
	return -1;
}


 

你可能感兴趣的:(使用ffmpeg+SDL的简单播放器)