List of SDL2 articles
Introduction to SDL2
SDL2 event processing
SDL2 texture rendering
SDL2 audio playback
FFmpeg+SDL2 video streaming
FFmpeg+SDL2 audio stream playback
FFmpeg audio and video synchronization
After the previous series of SDL2 learning, finally to achieve a complete simple player.
Threading model
This is the implementation of a simple player thread model, and through this diagram, combined with what we learned in the previous blog, we can basically understand the overall flow of the player. The specific code is also based on this diagram.
Important structure
VideoState
The most important structure in the whole player, demultiplexing, decoding, audio and video synchronization, render related parameters are in this structure, it runs through the whole playback process.
typedef struct VideoState {
char filename[1024]; // File name
AVFormatContext *pFormatCtx; / / context
int videoStream, audioStream; // Stream index
//// Synchronization correlation
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;
// Audio correlation
AVStream *audio_st; / / audio stream
AVCodecContext *audio_ctx; // Audio decoding context
PacketQueue audioq; // Audio queue
uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2]; // Audio cache
unsigned int audio_buf_size;
unsigned int audio_buf_index;
AVFrame audio_frame; / / audio frame
AVPacket audio_pkt; / / audio packets
uint8_t *audio_pkt_data;
int audio_pkt_size;
struct SwrContext *audio_swr_ctx; // Audio resampling
//video
AVStream *video_st; / / video
AVCodecContext *video_ctx; // Video stream decoding context
PacketQueue videoq; // Video stream queue
VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE]; // Decoded video frame array
int pictq_size, pictq_rindex, pictq_windex;
SDL_mutex *pictq_mutex;
SDL_cond *pictq_cond;
SDL_Thread *parse_tid; // Demultiplexing threads
SDL_Thread *video_tid;// Video decoder thread
int quit; // Exits the flag bit
} VideoState;
Copy the code
PacketQueue
//// Save queue of audio and video packet after demultiplexing
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;
int size;
SDL_mutex *mutex;
SDL_cond *cond;
} PacketQueue;
Copy the code
VideoPicture
//// Decoded video frame
typedef struct VideoPicture {
AVFrame *frame;
int width, height;
double pts; // The time when the video frame should play after audio and video synchronization
} VideoPicture;
Copy the code
Specific code
Main
- Initialize the
- Create a timer to schedule the refresh of video frames
- Create a demultiplexing thread
- Wait for events
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);
}
// Create SDL Window
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();
// Timer refresh, mainly used to control the video refresh
schedule_refresh(is, 40);
// Create a demultiplexing thread
is->parse_tid = SDL_CreateThread(demux_thread, "demux_thread", is);
if(! is->parse_tid) { av_free(is);return - 1;
}
for (;;) {
// Wait for SDL events, otherwise block
SDL_WaitEvent(&event);
switch (event.type) {
case FF_QUIT_EVENT:
case SDL_QUIT: / / exit
is->quit = 1;
goto Destroy;
case SDL_KEYDOWN:/ / ESC to exit
if (event.key.keysym.sym == SDLK_ESCAPE) {
is->quit = 1;
goto Destroy;
}
break;
case FF_REFRESH_EVENT: // Timer refresh event
video_refresh_timer(event.user.data1);
break;
default:
break; }}/ / exit
Destroy:
SDL_Quit();
return 0;
}
Copy the code
The solution to reuse
- Open the file
- Find audio and video streams
- Open audio and video streams, create video decoding threads, and prepare to decode
- Read the packet, put the audio and video packet into the queue respectively, and wait for the decoding thread to take out
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;
}
// Find the first video stream
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; }}// Open audio stream, create decoder, configure parameters
if (audio_index >= 0) {
stream_component_open(is, audio_index);
}
// Open video stream, create decoder, create decoder thread
if (video_index >= 0) {
stream_component_open(is, video_index);
// video_tid = SDL_CreateThread(decode_video_thread, "decode_video_thread", is);
}
for (;;) {
if (av_read_frame(is->pFormatCtx, packet) < 0) {
if (is->pFormatCtx->pb->error == 0) {
SDL_Delay(100); /* no error; wait for user input */
continue;
} else {
break; }}// Put the packet into the queue
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;
}
Copy the code
Video decoding
- Fetch the video packet from the queue
- Decoding, synchronization
- Frame is stored in the array and waits for the video to render
//// Video decoding
int decode_video_thread(void *arg) {
VideoState *is = (VideoState *) arg;
AVPacket pkt1, *packet = &pkt1;
AVFrame *pFrame;
double pts;
pFrame = av_frame_alloc();
for (;;) {
// Retrieve the packet from the video queue
if (packet_queue_get(&is->videoq, packet, 1) < 0) {
break;
}
/ / decoding
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);
/ / synchronize
pts = synchronize_video(is, pFrame, pts);
if (queue_picture(is, pFrame, pts) < 0) {
break;
}
av_packet_unref(packet);
}
}
av_frame_free(&pFrame);
return 0;
}
Copy the code
Audio decoding
//// Audio device callback
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 decoding
audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf), &pts);
if (audio_size < 0) {
// Audio decoding error, play mute
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;
// Remix playback
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; }}Copy the code
Video refresh play
//// Refresh the video and predict the playing time of the next frame and set a new timer
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 {
// Get a video frame from the array
vp = &is->pictq[is->pictq_rindex];
is->video_current_pts = vp->pts;
is->video_current_pts_time = av_gettime();
// Subtract the current Frame time from the last Frame time to get the time difference between the two frames
delay = vp->pts - is->frame_last_pts;
if (delay <= 0 || delay >= 1.0) {
// Delay less than 0 or more than 1 second (too long) is an error, set the delay time to the last delay time
delay = is->frame_last_delay;
}
// Save the delay and PTS for next use
is->frame_last_delay = delay;
is->frame_last_pts = vp->pts;
// Get audio Audio_Clock
ref_clock = get_audio_clock(is);
// Get the difference between the current PTS and Audio_Clock
diff = vp->pts - ref_clock;
// AV_SYNC_THRESHOLD Minimum refresh time
sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;
// Diff is lower than the non-synchronization threshold and can be synchronized
if (fabs(diff) < AV_NOSYNC_THRESHOLD) {
if (diff <= -sync_threshold) {
// The video should be played as soon as possible before the audio time
delay = 0;
} else if (diff >= sync_threshold) {
// After the audio time, the video should be delayed
delay = 2 * delay;
}
}
is->frame_timer += delay;
// Finally the actual delay time
actual_delay = is->frame_timer - (av_gettime() / 1000000.0);
if (actual_delay < 0.010) {
// If the delay time is too small, set the minimum value
actual_delay = 0.010;
}
// Reset the timer according to the delay time to refresh the video
schedule_refresh(is, (int) (actual_delay * 1000 + 0.5));
// Video frame display
video_display(is);
// Update the subscript of the video frame array
if (++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_rindex = 0;
}
SDL_LockMutex(is->pictq_mutex);
// Video frame array minus oneis->pictq_size--; SDL_CondSignal(is->pictq_cond); SDL_UnlockMutex(is->pictq_mutex); }}else {
schedule_refresh(is, 100); }}Copy the code
This is the general process, which will be much more complicated than the previous Demo, but all the knowledge is covered in the previous blog, and I can’t explain anything in the blog. It is better to run it directly by myself, and then look at the code. It will clarify the process, and the whole player code will not be difficult to understand. Github-SimplePlay is attached here
Learning audio and video recommendations:
The first of course recommended Thor Lei Xiaohua, China FFmpeg first person, systematically sorted out the FFmpeg related knowledge points, the entry will see, but early death, memory of thor. Lei Xiaohua’s blog
The second recommended god Li Chao moOCs network video, speaking very practical, audio and video xiaobai is worth a look.