SDL2文章列表
SDL2入门
SDL2事件处理
SDL2纹理渲染
SDL2音频播放
FFmpeg+SDL2实现视频流播放
FFmpeg+SDL2实现音频流播放
FFmpeg音视频同步
SDL2线程操作
经过前面一系列的SDL2学习,终于到最后实现一个完整的简易播放器了。
线程模型
这是实现的简易播放器的线程模型,通过这张图再结合我们之前博客中学习的内容,基本可以了解播放器的一个整体运行流程。具体代码也是根据这张图来实现。
重要结构体
VideoState
整个播放器中最重要的结构体,解复用、解码、音视频同步、渲染相关参数都在该结构体中,它贯穿了整个播放流程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| typedef struct VideoState { char filename[1024]; AVFormatContext *pFormatCtx; int videoStream, audioStream;
double audio_clock; double frame_timer; double frame_last_pts; double frame_last_delay;
double video_clock; double video_current_pts; int64_t video_current_pts_time;
AVStream *audio_st; AVCodecContext *audio_ctx; PacketQueue audioq; uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2]; unsigned int audio_buf_size; unsigned int audio_buf_index; AVFrame audio_frame; AVPacket audio_pkt; uint8_t *audio_pkt_data; int audio_pkt_size; struct SwrContext *audio_swr_ctx;
AVStream *video_st; AVCodecContext *video_ctx; PacketQueue videoq;
VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE]; int pictq_size, pictq_rindex, pictq_windex; SDL_mutex *pictq_mutex; SDL_cond *pictq_cond;
SDL_Thread *parse_tid; SDL_Thread *video_tid;
int quit; } VideoState;
|
PacketQueue
1 2 3 4 5 6 7 8
| typedef struct PacketQueue { AVPacketList *first_pkt, *last_pkt; int nb_packets; int size; SDL_mutex *mutex; SDL_cond *cond; } PacketQueue;
|
VideoPicture
1 2 3 4 5 6
| typedef struct VideoPicture { AVFrame *frame; int width, height; double pts; } VideoPicture;
|
具体代码
Main
- 初始化
- 创建定时器,定时视频帧的刷新
- 创建解复用线程
- 等待事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| int WinMain(int argc, char *argv[]) { char *file = "C:\\Users\\lenovo\\Desktop\\IMG_5950.mp4"; SDL_Event event; VideoState *is; is = av_mallocz(sizeof(VideoState));
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError()); exit(1); } win = SDL_CreateWindow("Media Player", 100, 100, 640, 480, SDL_WINDOW_RESIZABLE); if (!win) { fprintf(stderr, "SDL_CreateWindow error,exit!", SDL_GetError()); exit(1); }
renderer = SDL_CreateRenderer(win, -1, 0); text_mutex = SDL_CreateMutex(); strlcpy(is->filename, file, sizeof(is->filename)); is->pictq_mutex = SDL_CreateMutex(); is->pictq_cond = SDL_CreateCond();
schedule_refresh(is, 40);
is->parse_tid = SDL_CreateThread(demux_thread, "demux_thread", is); if (!is->parse_tid) { av_free(is); return -1; }
for (;;) { SDL_WaitEvent(&event); switch (event.type) { case FF_QUIT_EVENT: case SDL_QUIT: is->quit = 1; goto Destroy; case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_ESCAPE) { is->quit = 1; goto Destroy; } break; case FF_REFRESH_EVENT: video_refresh_timer(event.user.data1); break; default: break; } }
Destroy: SDL_Quit(); return 0;
}
|
解复用
- 打开文件
- 找到音视频流
- 打开音频、视频流,创建视频解码线程,准备解码
- 读取packet,将音视频packet分别放入队列中,等待解码线程取出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| int demux_thread(void *arg) { if ((err_code = avformat_open_input(&pFormatCtx, is->filename, NULL, NULL)) < 0) { av_strerror(err_code, errors, 1024); return -1; }
for (i = 0; i < pFormatCtx->nb_streams; i++) { if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && video_index < 0) { video_index = i; } if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0) { audio_index = i; } } if (audio_index >= 0) { stream_component_open(is, audio_index); } if (video_index >= 0) { stream_component_open(is, video_index); }
for (;;) { if (av_read_frame(is->pFormatCtx, packet) < 0) { if (is->pFormatCtx->pb->error == 0) { SDL_Delay(100); continue; } else { break; } } if (packet->stream_index == is->videoStream) { packet_queue_put(&is->videoq, packet); } else if (packet->stream_index == is->audioStream) { packet_queue_put(&is->audioq, packet); } else { av_packet_unref(packet); } } return 0; }
|
视频解码
- 从队列中取出视频packet
- 解码,同步
- 加码后Frame存入数组,等待视频渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| int decode_video_thread(void *arg) { VideoState *is = (VideoState *) arg; AVPacket pkt1, *packet = &pkt1; AVFrame *pFrame; double pts;
pFrame = av_frame_alloc();
for (;;) { if (packet_queue_get(&is->videoq, packet, 1) < 0) { break; } avcodec_send_packet(is->video_ctx, packet); while (avcodec_receive_frame(is->video_ctx, pFrame) == 0) { if ((pts = pFrame->best_effort_timestamp) != AV_NOPTS_VALUE) { } else { pts = 0; } pts *= av_q2d(is->video_st->time_base);
pts = synchronize_video(is, pFrame, pts); if (queue_picture(is, pFrame, pts) < 0) { break; } av_packet_unref(packet); } } av_frame_free(&pFrame); return 0; }
|
音频解码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| void audio_callback(void *userdata, Uint8 *stream, int len) {
VideoState *is = (VideoState *) userdata; int len1, audio_size; double pts;
SDL_memset(stream, 0, len);
while (len > 0) { if (is->audio_buf_index >= is->audio_buf_size) { audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf), &pts); if (audio_size < 0) { is->audio_buf_size = 1024 * 2 * 2; memset(is->audio_buf, 0, is->audio_buf_size); } else { is->audio_buf_size = audio_size; } is->audio_buf_index = 0; } len1 = is->audio_buf_size - is->audio_buf_index; if (len1 > len) len1 = len; SDL_MixAudio(stream, (uint8_t *) is->audio_buf + is->audio_buf_index, len1, SDL_MIX_MAXVOLUME); len -= len1; stream += len1; is->audio_buf_index += len1; } }
|
视频刷新播放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| void video_refresh_timer(void *userdata) {
VideoState *is = (VideoState *) userdata; VideoPicture *vp; double actual_delay, delay, sync_threshold, ref_clock, diff;
if (is->video_st) { if (is->pictq_size == 0) { schedule_refresh(is, 1); } else { vp = &is->pictq[is->pictq_rindex];
is->video_current_pts = vp->pts; is->video_current_pts_time = av_gettime(); delay = vp->pts - is->frame_last_pts; if (delay <= 0 || delay >= 1.0) { delay = is->frame_last_delay; } is->frame_last_delay = delay; is->frame_last_pts = vp->pts;
ref_clock = get_audio_clock(is); diff = vp->pts - ref_clock;
sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD; if (fabs(diff) < AV_NOSYNC_THRESHOLD) { if (diff <= -sync_threshold) { delay = 0; } else if (diff >= sync_threshold) { delay = 2 * delay; } } is->frame_timer += delay; actual_delay = is->frame_timer - (av_gettime() / 1000000.0); if (actual_delay < 0.010) { actual_delay = 0.010; } schedule_refresh(is, (int) (actual_delay * 1000 + 0.5));
video_display(is);
if (++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) { is->pictq_rindex = 0; } SDL_LockMutex(is->pictq_mutex); is->pictq_size--; SDL_CondSignal(is->pictq_cond); SDL_UnlockMutex(is->pictq_mutex); } } else { schedule_refresh(is, 100); } }
|
大体的流程就是这样了,相比之前的Demo复杂度会高不少,但是所有的知识在前面的博客中都有涉及,在博客中也讲不了什么东西,还是直接自己运行,再去看代码会更好,理清流程,整个播放器的代码也不会很难看懂,这里附上源码 Github-SimplePlay
学习音视频推荐:
第一个当然推荐雷神雷霄骅,中国FFmpeg第一人,系统地整理了FFmpeg相关的知识点,入门必看,可惜早逝,缅怀雷神。雷霄骅的博客
第二个推荐大神李超的慕课网视频,讲得非常实用,音视频小白入门值得一看。