Analysis of the overall framework of ffplay source code analysis ffmpeg decoding (stream_component_open, avformat_find_stream_info, event_loop, stream_open)

Posted by Adastra on Fri, 18 Feb 2022 08:36:52 +0100

Recently, in the study of ffmpeg and ffplay, it is found that the source code analysis of ffmpeg unpacking on the Internet is not much and complete. Based on the ffplay source code, let's explain the ffmpeg decoding process. Here we mainly explain the key variables in the data structures of AVFormatContext, AVStream and AVIOContext, such as void*priv_data, AVStreamInternal *internal, AVFormatInternal *internal and other internal variables are the most important, because it is these internal non-public variables that are the key to storing file content, important interfaces or attributes and transferring between interfaces. To understand the true meaning of ffmpeg library to define these internal variables is the key to learning ffmpeg.
However, there are too many interfaces to write in one blog post, so students need to pay attention to other blog posts

All interfaces and data structures are written in great detail, but they have been studied for a long time. It's very troublesome and tiring to write. After reading it, give me some attention, ha ha ha ha

Key Tips:

  1. Many structures in ffmpeg (AVStream, URLContext, AVFormatContext) like to use void priv_data or voidopaque variable
    In fact, this variable is used to store the "substructure" of the structure. I prefer to call it external protocol or external resource (interface or data). For example, in ffplay, AVFormatContext needs to be unpacked. By detecting the file format, it is concluded that AVInputFormat is mov(MP4) format, and AVFormatContext - > priv_data = movcontext variable (mov.c file), priv in AVStream_ Data is used to store mov protocol movstramcontext structure variables, priv in URLContext_ Data is used to store the FileContext structure in the file protocol. In fact, this is to separate the protocol interface function or data from the backbone interface, so as to make the whole library scalable. So you will find that the priv of the backbone will be mentioned at the beginning of various protocol interfaces_ Data is assigned to the structure of the protocol itself. For example: mov_ read_ In header
    MOVContext *mov = s->priv_data; Writing in this way is also a kind of grammatical sugar. sc will not be affected by priv_data name.
    Even if the name of external variables changes, it will have little impact on the internal interface. ffmpeg interfaces mostly use this method, especially involving some external protocols
    rtmp streaming media, file file, mov format, etc.

  2. For the naming of Context, such as URLContext, FileContext, AVFormatContext, etc., my personal understanding is the data + method (Interface) required to complete the function. For example, there is a file protocol in URLContext. In the FileContext structure, there are open, close, read and other methods and uint*data variables to store the data read from the file. This is stored level by level. In order to make the code more extensible, this kind of library is written by many people. I don't know if I can explain clearly, ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha ha.

  3. For internal, such naming as AVStreamInternal is generally used to store data and pass it to the interface. For example, AVStreamInternal is used to store the information (location, size, pts, dts) and codec data interface of audio and video samples, and AVFormatInternal is used to store (obtained through AVStreamInternal index sample) AVPacket content. It is mainly used to store and transfer data.

Here I would like to focus on my personal understanding of AVFormatContext:
AVFormatContext structure is the structure of ffmpeg docking and unpacking, that is, directly reading (whether local file file or network streaming media) files or resources, and according to certain structure requirements (AVPacket is used to store a frame or a sample information)
To organize and store the data read out from the multimedia file. Decode and store the information in the form of AVPacket structure. Therefore, in AVFormatContext, since it is necessary to unpack, it is necessary to have the file IO interface AVIOContext and the audio and video metadata information generated when reading the multimedia file head information in AVStream. It must be noted that both AVIOContext and AVStream are lower layer protocols for docking, such as AVIOContext - > priv_ data=FileContext(file.c)
URLProtocol ff_file_protocol stores the interface for reading files, and FileContext can store file handles, avstream - > internal - > index_ Entries is used to store everyone's sample information. However, after reading a sample (such as mov(mp4)), find the information of each sample by reading the head and store it in avstream - > internal - > index_ Entries), which need to be stored in avformatinternal - > avpacketlist * packet in the form of AVPacket_ buffer_ End, and then decode.
Therefore, from this point of view, the upper structure of AVFormatContext is not directly connected with the lower protocols (FileContext, MOVContext, MOVStreamContext), which I mentioned in the above Tips and previous blog posts. The ffmpeg library needs to be written by many people and must be extensible. Changes in the lower layer will not affect the upper structure.

In fact, the naming of ffmpeg is also very reasonable. AVFormatContext is simple to read files and put them in the linked list avformatinternal - > avpacketlist * packet_ buffer_ End, wait for decoding. AVStream and AVIOContext serve this purpose.

This blog post mainly explains the overall framework of ffplay, including creating thread decoding, image display after decoding, etc

//Entry function main() function
int main(int argc, char **argv)
{
   int flags;
    VideoState *is;
    ...
    //Initialize refresh frame flush_pkt, when flush_pkt packet_ queue_ put_ After the private value is assigned, the queue serial number is increased by one, that is, it is regarded as a new queue
    //There is an introduction in the blog post later
    av_init_packet(&flush_pkt);
    flush_pkt.data = (uint8_t *)&flush_pkt;

    if (!display_disable) {
        int flags = SDL_WINDOW_HIDDEN;
...
        //Create window (SDL library interface, which actually calls CreateWindow of window API to create window handle)
        window = SDL_CreateWindow(program_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, default_width, default_height, flags);
        SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
        if (window) {
        //create render 
            renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
            if (!renderer) {
                av_log(NULL, AV_LOG_WARNING, "Failed to initialize a hardware accelerated renderer: %s\n", SDL_GetError());
                renderer = SDL_CreateRenderer(window, -1, 0);
            }
            if (renderer) {
                if (!SDL_GetRendererInfo(renderer, &renderer_info))
                    av_log(NULL, AV_LOG_VERBOSE, "Initialized %s renderer.\n", renderer_info.name);
            }
        }
        if (!window || !renderer || !renderer_info.num_texture_formats) {
            av_log(NULL, AV_LOG_FATAL, "Failed to create window or renderer: %s", SDL_GetError());
            do_exit(NULL);
        }
    }

   //According to input_filename file name, find out the corresponding protocol, unpack it, and create a thread for decoding
    is = stream_open(input_filename, file_iformat); 
    if (!is) {
        av_log(NULL, AV_LOG_FATAL, "Failed to initialize VideoState!\n");
        do_exit(NULL);
    }

    //Loop playing decoded pictures
    event_loop(is);

    /* never returns */

    return 0;
}

According to input_filename file name, find out the corresponding protocol, unpack it, and create a thread for decoding

Tips: let's say that all parameters of ffplay framework are in VideoState, which is also the top-level structure, including understanding and encapsulating AVFormatContext, decoding AVCodecInternal, etc. as mentioned above, for the convenience of coding, the upper data structure is not connected with the lower data structure. You will find that when the upper data is transferred into the interface, It will be re assigned to a new structure of the same type, so that when any variable name changes, the interface function will not be affected

//Queue structure to be decoded after unsealing
typedef struct PacketQueue {
    //first_pkt is the head of the queue. Every time a packet is taken from the queue, it is taken from the first_pkt inside
    //last_pkt variable is the tail pointer of MyAVPacketList when last_ When pkt is empty, it will be given to first_pkt assignment packet
    MyAVPacketList *first_pkt, *last_pkt;
    int nb_packets;//How many packet s are there in the queue
    int size; //Queue size
    int64_t duration; //Queue duration, that is, the duration of the packet
    int abort_request; //Discard assignment ID
    int serial; //Queue serial number
    SDL_mutex *mutex; //The lock of the queue, because the assignment of the queue is in multithreading
    SDL_cond *cond; //Semaphores are used for synchronization
} PacketQueue;

//According to input_filename file name, find out the corresponding protocol, unpack it, and create a thread for decoding
static VideoState *stream_open(const char *filename, AVInputFormat *iformat)
{
    VideoState *is;
    
    is = av_mallocz(sizeof(VideoState));//Open up memory space
    if (!is)
        return NULL;
    //Assign stream serial number. Generally, there are only two streams of audio and video
    is->last_video_stream = is->video_stream = -1;
    is->last_audio_stream = is->audio_stream = -1;
    is->last_subtitle_stream = is->subtitle_stream = -1;
    is->filename = av_strdup(filename);
    if (!is->filename)
        goto fail;
    is->iformat = iformat;
    is->ytop    = 0;
    is->xleft   = 0;

    /* start video display */
    //Initialize the decoded image display queue FrameQueue, including opening up the queue memory space, creating the mutex and semaphore SDL of the image display queue FrameQueue_ cond
    if (frame_queue_init(&is->pictq, &is->videoq, VIDEO_PICTURE_QUEUE_SIZE, 1) < 0)
        goto fail;
    if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
        goto fail;
    if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
        goto fail;

    //Initialize the packet queue used for decoding after unpacking, including the mutex and semaphore SDL that create the PacketQueue_ cond
    if (packet_queue_init(&is->videoq) < 0 ||
        packet_queue_init(&is->audioq) < 0 ||
        packet_queue_init(&is->subtitleq) < 0)
        goto fail;

   //Create read_ Semaphore of thread interface
    if (!(is->continue_read_thread = SDL_CreateCond())) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        goto fail;
    }

   //Initialize the playback time of audio and video
    init_clock(&is->vidclk, &is->videoq.serial);
    init_clock(&is->audclk, &is->audioq.serial);
    init_clock(&is->extclk, &is->extclk.serial);
    is->audio_clock_serial = -1;
   ...
    is->audio_volume = startup_volume;
    is->muted = 0;
    is->av_sync_type = av_sync_type;
    //Create thread read_thread is used to find out the corresponding protocol, unpack it, and create thread decoding
    is->read_tid     = SDL_CreateThread(read_thread, "read_thread", is);
    if (!is->read_tid) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateThread(): %s\n", SDL_GetError());
fail:
        stream_close(is);
        return NULL;
    }
    return is;
}
//Initialize the decoded image display queue FrameQueue, including opening up the queue memory space, creating the mutex and semaphore SDL of the image display queue FrameQueue_ cond
static int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last)
{
    int i;
    memset(f, 0, sizeof(FrameQueue)); //The initialization variable is 0
    if (!(f->mutex = SDL_CreateMutex())) { //Create mutex
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    if (!(f->cond = SDL_CreateCond())) {//Create SDL_cond
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    f->pktq = pktq;
    f->max_size = FFMIN(max_size, FRAME_QUEUE_SIZE);
    f->keep_last = !!keep_last;
    for (i = 0; i < f->max_size; i++)//Open up queue memory
        if (!(f->queue[i].frame = av_frame_alloc()))
            return AVERROR(ENOMEM);
    return 0;
}

//Initialize the packet queue used for decoding after unpacking, including the mutex and semaphore SDL that create the PacketQueue_ cond
static int packet_queue_init(PacketQueue *q)
{
    memset(q, 0, sizeof(PacketQueue));//The initialization variable is 0
    q->mutex = SDL_CreateMutex();//Create mutex
    if (!q->mutex) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    q->cond = SDL_CreateCond();//Create SDL_cond
    if (!q->cond) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    q->abort_request = 1;
    return 0;
}

Thread read_thread is used to find out the corresponding protocol, unpack it, and create thread decoding
See for details Analysis of ffplay source code avformat of ffmpeg decoding process_ find_ stream_ info,read_frame_internal,avpriv_packet_list_put interface) It is introduced in
ffplay source code analysis stream of ffmpeg decoding process_ component_ open,avcodec_receive_frame,queue_picture,packet_queue_get interface) It is introduced in

static int read_thread(void *arg)
{
   //arg is assigned to a temporary variable for coding convenience
    VideoState *is = arg;
    AVFormatContext *ic = NULL;
    int err, i, ret;
    int st_index[AVMEDIA_TYPE_NB]; //stream index (type)
    AVPacket pkt1, *pkt = &pkt1;
    int64_t stream_start_time;
    int pkt_in_play_range = 0;
    AVDictionaryEntry *t;
    SDL_mutex *wait_mutex = SDL_CreateMutex();
    int scan_all_pmts_set = 0;
    int64_t pkt_ts;

    memset(st_index, -1, sizeof(st_index));
    is->eof = 0;

    //AVFormatContext unpacks context and opens up memory
    //AVFormatInternal *internal memory
    //Assignment interface io_open is used to find out the corresponding IO external protocol (file,rtmp) according to the file name
    ic = avformat_alloc_context(); 
    ...

    //Find out the io external protocol and assign it to AVIOContext + find out the external AVInputFormat(mov) and read the header
    err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
    if (err < 0) {
        print_error(is->filename, err);
        ret = -1;
        goto fail;
    }
   ...
    is->ic = ic;

    av_format_inject_global_side_data(ic);

    if (find_stream_info) {
        AVDictionary **opts = setup_find_stream_info_opts(ic, codec_opts);
        int orig_nb_streams = ic->nb_streams;

        //Read some packet s, assign the decoder interface, and assign some parameters from AVStream to AVCodecContext*Internal for subsequent decoding
        //See the link above for details
        ffplay Source code analysis ffmpeg Decoding process avformat_find_stream_info,read_frame_internal,avpriv_packet_list_put Interface
        err = avformat_find_stream_info(ic, opts);
       ....
    }

    /* if seeking requested, we execute it */
    if (start_time != AV_NOPTS_VALUE) {
        int64_t timestamp;

        timestamp = start_time;
        /* add the stream start time */
        if (ic->start_time != AV_NOPTS_VALUE)
            timestamp += ic->start_time;
        ret = avformat_seek_file(ic, -1, INT64_MIN, timestamp, INT64_MAX, 0);
        if (ret < 0) {
            av_log(NULL, AV_LOG_WARNING, "%s: could not seek to position %0.3f\n",
                    is->filename, (double)timestamp / AV_TIME_BASE);
        }
    }

    is->realtime = is_realtime(ic);

    if (show_status)
        av_dump_format(ic, 0, is->filename, 0);

   //Generally, two streams audio and video streams are created through trak(mov format)
    for (i = 0; i < ic->nb_streams; i++) {
        AVStream *st = ic->streams[i];
        enum AVMediaType type = st->codecpar->codec_type;
        st->discard = AVDISCARD_ALL;
        if (type >= 0 && wanted_stream_spec[type] && st_index[type] == -1)
            if (avformat_match_stream_specifier(ic, st, wanted_stream_spec[type]) > 0)
                st_index[type] = i;
    }
    for (i = 0; i < AVMEDIA_TYPE_NB; i++) {
        if (wanted_stream_spec[i] && st_index[i] == -1) {
            av_log(NULL, AV_LOG_ERROR, "Stream specifier %s does not match any %s stream\n", wanted_stream_spec[i], av_get_media_type_string(i));
            st_index[i] = INT_MAX;
        }
    }
    ......

    is->show_mode = show_mode;
    if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
        AVStream *st = ic->streams[st_index[AVMEDIA_TYPE_VIDEO]];
        AVCodecParameters *codecpar = st->codecpar;
        AVRational sar = av_guess_sample_aspect_ratio(ic, st, NULL);
        if (codecpar->width)
            set_default_window_size(codecpar->width, codecpar->height, sar);
    }

    /* open the streams */
//stream_ The index parameter is the serial number of the stream. Generally, there are two stream serial numbers 0 or 1, one audio and one video
//One thread for audio and one thread for video are used to continuously obtain samples (packets) from is - > decoder * D - > packetqueue * q and decode them
//Obtain one sample at a time and decode it, and then obtain one sample and decode it
//Audio starts playing but not decoding in a thread
    if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {//audio frequency
        stream_component_open(is, st_index[AVMEDIA_TYPE_AUDIO]);
    }

    ret = -1;
    if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {//video
        ret = stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]);
    }
    if (is->show_mode == SHOW_MODE_NONE)
        is->show_mode = ret >= 0 ? SHOW_MODE_VIDEO : SHOW_MODE_RDFT;

    if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0) {
        stream_component_open(is, st_index[AVMEDIA_TYPE_SUBTITLE]);
    }
    ...
    
    //Small Tips: for (;) Because in the compilation phase, the assembler will put for (;) Optimize it and enter the cycle directly
    //Loop read packet
    //Audio and video samples are interspersed and encapsulated, so according to movstreamcontext - > current_ sample, keep calling AV_ read_ Just frame
    //current_ There will be no duplication in the sample. This is the principle for mov format to take the packet and find the packet
    for (;;) {
        if (is->abort_request)
        {
            break;
        }
        if (is->paused != is->last_paused) {
            is->last_paused = is->paused;
            if (is->paused)
            {
                is->read_pause_return = av_read_pause(ic);
            }
            else
            {
                av_read_play(ic);
            }
        }
...
       //Handle fast forward, fast backward and pause, that is, find a specific sample to decode and play according to the current playback time
        if (is->seek_req) {
            int64_t seek_target = is->seek_pos; //The number of seconds you need to find is converted to PTS_ Seek assignment
            int64_t seek_min    = is->seek_rel > 0 ? seek_target - is->seek_rel + 2: INT64_MIN;
            int64_t seek_max    = is->seek_rel < 0 ? seek_target - is->seek_rel - 2: INT64_MAX;

//According to the protocol in avformatcontext * IC - > avinputformat (such as mov format) and the pts value seek of the sample to be searched_ target
//Call the corresponding external protocol interface read_ seek(mov_read_seek)
//mov_read_seek function: Seek_ The value of target matches the pts of all samples, finds out the corresponding sample index, and assigns movstreamcontext - > current_ sample
//AV below_ read_ The frame will directly read the sample to be found, because av_read_frame will read the current every time_ sample
ffmpeg Unpacking mov/mp4 Analysis of format unpacking source code mov_read_header(read metadata),mov_read_packet(read sample data),mov_read_trak)It is introduced in
//ffplay takes the time of audio playback callback as pts value
            ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);
            if (ret < 0) {
                av_log(NULL, AV_LOG_ERROR,
                       "%s: error while seeking\n", is->ic->url);
            } else {
                //Empty all packet s to be decoded in the existing queue
                if (is->audio_stream >= 0) {
                   //Clear the variables in the PacketQueue queue and initialize to 0
                    packet_queue_flush(&is->audioq);
                    //Flush will be refreshed_ Pkt packet is assigned to the queue PacketQueue, indicating that a queue is restarted, and the queue serial number is added by one
                    packet_queue_put(&is->audioq, &flush_pkt);
                }
                if (is->subtitle_stream >= 0) {
                    packet_queue_flush(&is->subtitleq);
                    packet_queue_put(&is->subtitleq, &flush_pkt);
                }
                if (is->video_stream >= 0) {
                    //Clear the variables in the PacketQueue queue and initialize to 0
                    packet_queue_flush(&is->videoq);
                    //Flush will be refreshed_ Pkt packet is assigned to the queue PacketQueue, indicating that a queue is restarted, and the queue serial number is added by one
                    packet_queue_put(&is->videoq, &flush_pkt);
                }   
                if (is->seek_flags & AVSEEK_FLAG_BYTE) {
                   set_clock(&is->extclk, NAN, 0);
                } else {
                //The pts value seek of the sample to be searched_ Target, assigned to is - > extclk as a temporary storage
                   set_clock(&is->extclk, seek_target / (double)AV_TIME_BASE, 0);
                }
            }
            is->seek_req = 0;
            is->queue_attachments_req = 1;
            is->eof = 0;
            if (is->paused)
                step_to_next_frame(is);
        }
       ...

        /* if the queue are full, no need to read more */
        if (infinite_buffer<1 &&
              (is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE
            || (stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq) &&
                stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq) &&
                stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq)))) {
            /* wait 10 ms */
            SDL_LockMutex(wait_mutex);
            SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
            SDL_UnlockMutex(wait_mutex);
            continue;
        }
        ...
        //Read a sample data from the file according to the protocol interface and the sample information read in the file head
 ffplay Source code analysis ffmpeg Decoding process avformat_find_stream_info,read_frame_internal,avpriv_packet_list_put It is introduced in the interface
        ret = av_read_frame(ic, pkt);
        if (ret < 0) {
            if ((ret == AVERROR_EOF || avio_feof(ic->pb)) && !is->eof) {
                if (is->video_stream >= 0)
                   //When the stream exists, assign an empty packet to the PacketQueue queue
                    packet_queue_put_nullpacket(&is->videoq, is->video_stream);
                if (is->audio_stream >= 0)
                    packet_queue_put_nullpacket(&is->audioq, is->audio_stream);
                if (is->subtitle_stream >= 0)
                    packet_queue_put_nullpacket(&is->subtitleq, is->subtitle_stream);
                is->eof = 1;
            }
            if (ic->pb && ic->pb->error) {
                if (autoexit)
                    goto fail;
                else
                    break;
            }
            SDL_LockMutex(wait_mutex);
            SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
            SDL_UnlockMutex(wait_mutex);
            continue;
        } else {
            is->eof = 0;
        }
        /* check if packet is in play range specified by user, then queue, otherwise discard */
        stream_start_time = ic->streams[pkt->stream_index]->start_time; //First dts
        pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts;
        //ppkt_in_play_range is 1
        pkt_in_play_range = duration == AV_NOPTS_VALUE ||
                (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) *
                av_q2d(ic->streams[pkt->stream_index]->time_base) -
                (double)(start_time != AV_NOPTS_VALUE ? start_time : 0) / 1000000
                <= ((double)duration / 1000000);
        //Because the audio and video are stored alternately in the file, the packet may be audio or video and stored in different queues for decoding
 //In stream_ component_ open->video_ thread->get_ video_ frame->decoder_ decode_ frame->packet_ queue_ Get packet from get
        if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {
            //Will AV_ read_ The packet taken out by frame is assigned to the PacketQueue queue through packet_queue_get get and decode
            packet_queue_put(&is->audioq, pkt);
        } else if (pkt->stream_index == is->video_stream && pkt_in_play_range
                   && !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) {
            //Will AV_ read_ The packet taken out by frame is assigned to the PacketQueue queue through packet_queue_get get and decode
            packet_queue_put(&is->videoq, pkt);
        } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {
            packet_queue_put(&is->subtitleq, pkt);
        } else {
            av_packet_unref(pkt);
        }
    }

    ret = 0;
 fail:
    if (ic && !is->ic)
        avformat_close_input(&ic);

    if (ret != 0) {
        SDL_Event event;

        event.type = FF_QUIT_EVENT;
        event.user.data1 = is;
        SDL_PushEvent(&event);
    }
    SDL_DestroyMutex(wait_mutex);
    return 0;
}
//Will AV_ read_ The packet taken out by frame is assigned to the PacketQueue queue through packet_queue_get get and decode
static int packet_queue_put(PacketQueue *q, AVPacket *pkt)
{
    int ret;

    SDL_LockMutex(q->mutex);//Lock
    ret = packet_queue_put_private(q, pkt);
    SDL_UnlockMutex(q->mutex);

    if (pkt != &flush_pkt && ret < 0)
        av_packet_unref(pkt);

    return ret;
}

Important Tips:
As mentioned above, avformatinternal - > AVPacketList is used to store the unpacked data. The assignment of AVPacketList adopts the method of queue, and ffplay imitates this method, so let's combine the two here

ffplay queue assignment and acquisition

//ffplay queue structure
typedef struct PacketQueue {
    //first_pkt is the head of the queue. Every time a packet is taken from the queue, it is taken from the first_pkt inside
    //last_pkt variable is the tail pointer of MyAVPacketList. After inserting elements into the new linked list, q - > last_pkt automatically moves backward,
    //When last_ When pkt is empty, it will be given to first_pkt assignment packet
    MyAVPacketList *first_pkt, *last_pkt;
    int nb_packets;//How many packet s are there in the queue
    int size; //Queue size
    int64_t duration; //Queue duration, that is, the duration of the packet
    int abort_request; //Discard assignment ID
    int serial; //Queue serial number
    SDL_mutex *mutex; //The lock of the queue, because the assignment of the queue is in multithreading
    SDL_cond *cond; //Semaphores are used for synchronization
} PacketQueue;
//To packetqueue * q - > first_ Pkt linked list assignment
//Activate semaphore for decoding
//This interface is modeled after avpriv in ffmpeg_ packet_ list_ Put interface
//Where Q - > last_ Pkt variable is the tail pointer of MyAVPacketList. After inserting elements into the new linked list, q - > last_ Pkt auto move backward
//When last_ When pkt is NULL, add packet to first_ In pkt, you need to read first every time_ Pkt content
static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
{
    MyAVPacketList *pkt1;

    if (q->abort_request)//Discard assignment ID
       return -1;

    pkt1 = av_malloc(sizeof(MyAVPacketList));//Linked list opens up memory
    if (!pkt1)
        return -1;
    pkt1->pkt = *pkt;
    pkt1->next = NULL;
    if (pkt == &flush_pkt)//If the packet is refreshed, the sequence number of the queue is increased by one, that is, it is regarded as a new queue
        q->serial++;
    pkt1->serial = q->serial; //Update the queue where pkt1 is located

   //q->last_ Pkt variable can be regarded as an identifier
   //When last_ When pkt is NULL, add packet to first_ In pkt, you need to read first every time_ Pkt content
    if (!q->last_pkt)
        q->first_pkt = pkt1;
    else
        q->last_pkt->next = pkt1;
    q->last_pkt = pkt1;//Move last_pkt linked list pointer
    q->nb_packets++;  //The number of packs this queue already has
    q->size += pkt1->pkt.size + sizeof(*pkt1);
    q->duration += pkt1->pkt.duration; //Queue duration
    /* XXX: should duplicate packet data in DV case */
    SDL_CondSignal(q->cond); //Activate semaphore for decoding
    return 0;
}
//Get packet from queue
//When first_ When pkt is null, the tail pointer of MyAVPacketList is last_pkt assignment is null
//If it's first_ No value in pkt, thread waiting, waiting for packet_ queue_ put_ First in private_ SDL after pkt assignment_ Condsignal (Q - > cond) to activate
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial)
{
    MyAVPacketList *pkt1;
    int ret;

    SDL_LockMutex(q->mutex); //Lock

    //Small Tips: for (;) Because in the compilation phase, the assembler will put for (;) Optimize it and enter the cycle directly
    for (;;) {
        if (q->abort_request) { //Whether to abandon the queue
            ret = -1;
            break;
        }

        pkt1 = q->first_pkt; //List first_pkt removal
        if (pkt1) {
            q->first_pkt = pkt1->next;//Move linked list pointer
            if (!q->first_pkt)
            //When first_ When pkt is empty, last will be identified_ Pkt assignment is null
                q->last_pkt = NULL;
            q->nb_packets--;//Packets in the queue minus one
            q->size -= pkt1->pkt.size + sizeof(*pkt1);//Subtract the number of packet s
            q->duration -= pkt1->pkt.duration;//Minus total time
            *pkt = pkt1->pkt;//packet assignment
            if (serial)
            //Assign a value to the serial number of the retrieved packet
                *serial = pkt1->serial;
            av_free(pkt1);
            ret = 1;
            break;
        } else if (!block) {
            ret = 0;
            break;
        } else {
 //If it's first_ No value in pkt, thread waiting, waiting for packet_ queue_ put_ First in private_ SDL after pkt assignment_ Condsignal (Q - > cond) to activate
            SDL_CondWait(q->cond, q->mutex);
        }
    }
    SDL_UnlockMutex(q->mutex);
    return ret;
}

ffmpeg queue assignment and acquisition

//plast_pktl variable is the tail pointer of AVPacketList. After inserting elements into the new linked list, plast_pktl moves backward automatically
//When plast_ When pktl is NULL, add packet to packet_ In the buffer, you need to read the packet every time_ Buffer content
int avpriv_packet_list_put(AVPacketList **packet_buffer, AVPacketList **plast_pktl , AVPacket *pkt,
                                            int (*copy)(AVPacket *dst,  const AVPacket *src), int flags)
{
    AVPacketList *pktl = av_mallocz(sizeof(AVPacketList));//Open up memory space
    int ret;

    if (!pktl)
        return AVERROR(ENOMEM);

    if (copy) {
        ret = copy(&pktl->pkt, pkt);
        if (ret < 0) {
            av_free(pktl);
            return ret;
        }
    } else {
        //Assign the value of pkt - > data to pkt - > buf - > data
        ret = av_packet_make_refcounted(pkt);
        if (ret < 0) {
            av_free(pktl);
            return ret;
        }
        av_packet_move_ref(&pktl->pkt, pkt);
    }
   //plast_pktl variable is the tail pointer of AVPacketList. After inserting elements into the new linked list, plast_pktl moves backward automatically
   //When plast_ When pktl is NULL, add packet to packet_ In the buffer, you need to read the packet every time_ Buffer content
    if (*packet_buffer) 
        (*plast_pktl)->next = pktl;
    else
        *packet_buffer = pktl; //When plast_ When pktl is empty, pktl is assigned to packet_buffer 

    /* Add the packet in the buffered packet list. */
    *plast_pktl = pktl; //Move linked list pointer
    return 0;
}
//Get queue packet
//When pkt_ When buffer is null, AVPacketList tail pointer pkt_buffer_end assignment is null
int avpriv_packet_list_get(AVPacketList **pkt_buffer,
                           AVPacketList **pkt_buffer_end,
                           AVPacket      *pkt)
{
    AVPacketList *pktl;
    if (!*pkt_buffer)
        return AVERROR(EAGAIN);
    pktl        = *pkt_buffer;
    *pkt        = pktl->pkt;
    *pkt_buffer = pktl->next;//Move linked list pointer
    if (!pktl->next)
    //When pkt_ When buffer is null, the AVPacketList tail pointer pkt_buffer_end assignment is null
        *pkt_buffer_end = NULL;
    av_freep(&pktl);
    return 0;
}

Loop playing decoded pictures

//Loop playing decoded pictures
static void event_loop(VideoState *cur_stream)
{
    SDL_Event event;
    double incr, pos, frac;

    //Small Tips: for (;) Because in the compilation phase, the assembler will put for (;) Optimize it and enter the cycle directly
    for (;;) {
        double x;
        //Play pictures and audio
        refresh_loop_wait_event(cur_stream, &event);
        //Many messages returned by the Windows message mechanism call the SDL interface
        switch (event.type) {
        case SDL_KEYDOWN:
            if (exit_on_keydown || event.key.keysym.sym == SDLK_ESCAPE || event.key.keysym.sym == SDLK_q) {
            //Exit program
                do_exit(cur_stream);
                break;
            }
            
            The middle part of the students have a look. Ha, I won't repeat it here. I mainly talk about fast forward, fast backward and pause
            case SDLK_p:
            case SDLK_SPACE:
                toggle_pause(cur_stream);
                ...
            case SDLK_LEFT: //Left arrow rewind
                incr = seek_interval ? -seek_interval : -10.0; //Default - 10.0 seconds
                goto do_seek;
            case SDLK_RIGHT: //Right arrow fast forward
                incr = seek_interval ? seek_interval : 10.0;//Default 10.0 seconds
                goto do_seek;
            case SDLK_UP: //Up arrow fast forward
                incr = 60.0;//Default 60.0 seconds
                goto do_seek;
            case SDLK_DOWN: //Down arrow rewind
                incr = -60.0;//Default - 60.0 seconds
            do_seek:
            //Fast forward, fast backward
                    if (seek_by_bytes) { //Set a fast forward parameter
                        pos = -1;
                        if (pos < 0 && cur_stream->video_stream >= 0)
                            pos = frame_queue_last_pos(&cur_stream->pictq);
                        if (pos < 0 && cur_stream->audio_stream >= 0)
                            pos = frame_queue_last_pos(&cur_stream->sampq);
                        if (pos < 0)
                            pos = avio_tell(cur_stream->ic->pb);
                        if (cur_stream->ic->bit_rate)
                            incr *= cur_stream->ic->bit_rate / 8.0;
                        else
                            incr *= 180000.0;
                        pos += incr;
                        stream_seek(cur_stream, pos, incr, 1);
                    } else {
                       //Get the time information of the frame during playback
                       //If it is a pause, it is the current pts
                        pos = get_master_clock(cur_stream);
                        //Check whether pos is valid
                        if (isnan(pos))
                            pos = (double)cur_stream->seek_pos / AV_TIME_BASE;
                        //Add fast forward time
                        pos += incr;
                        if (cur_stream->ic->start_time != AV_NOPTS_VALUE && pos < cur_stream->ic->start_time / (double)AV_TIME_BASE)
                            pos = cur_stream->ic->start_time / (double)AV_TIME_BASE;
                        //Assign the pts of the sample to be searched to cur_stream in order to find
                        stream_seek(cur_stream, (int64_t)(pos * AV_TIME_BASE), (int64_t)(incr * AV_TIME_BASE), 0);
                    }
                break;
        ...
        }
    }
}
//Play pictures and audio
static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) {
    double remaining_time = 0.0;
    SDL_PumpEvents();
    //Non blocking message loop
    while (!SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) {
        if (!cursor_hidden && av_gettime_relative() - cursor_last_shown > CURSOR_HIDE_DELAY) {
            SDL_ShowCursor(0);
            cursor_hidden = 1;
        }
        if (remaining_time > 0.0)
            av_usleep((int64_t)(remaining_time * 1000000.0));
        remaining_time = REFRESH_RATE; //How often do I play a frame (FPS frame rate) 
        if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
            video_refresh(is, &remaining_time);
        SDL_PumpEvents();
    }
}
static void video_refresh(void *opaque, double *remaining_time) //remaining_time how long does it take to play a frame (FPS frame rate)
{
    VideoState *is = opaque;
    double time;

    Frame *sp, *sp2;

    if (!is->paused && get_master_sync_type(is) == AV_SYNC_EXTERNAL_CLOCK && is->realtime)
        check_external_clock_speed(is);

    if (!display_disable && is->show_mode != SHOW_MODE_VIDEO && is->audio_st) {
        time = av_gettime_relative() / 1000000.0;
        if (is->force_refresh || is->last_vis_time + rdftspeed < time) {
            video_display(is);
            is->last_vis_time = time;
        }
        *remaining_time = FFMIN(*remaining_time, is->last_vis_time + rdftspeed - time);
    }

    if (is->video_st) {
retry:
        if (frame_queue_nb_remaining(&is->pictq) == 0) {
            // nothing to do, no picture to display in the queue
        } else {
            double last_duration, duration, delay;
            Frame *vp, *lastvp;

            /* dequeue the picture */
            lastvp = frame_queue_peek_last(&is->pictq);
            vp = frame_queue_peek(&is->pictq);

            if (vp->serial != is->videoq.serial) {
                frame_queue_next(&is->pictq);
                goto retry;
            }

            if (lastvp->serial != vp->serial)
                is->frame_timer = av_gettime_relative() / 1000000.0;

            if (is->paused)
                goto display;

            /* compute nominal last_duration */
            last_duration = vp_duration(is, lastvp, vp);
            delay = compute_target_delay(last_duration, is);

            time= av_gettime_relative()/1000000.0;
            if (time < is->frame_timer + delay) {
                *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
                goto display;
            }
            SDL_LockMutex(is->pictq.mutex);
            if (!isnan(vp->pts))
            //Update and set the pts unit of the video frame as second, and pass the pts of the current frame through set_ Assign clock to is - > vidclk
                update_video_pts(is, vp->pts, vp->pos, vp->serial);
            SDL_UnlockMutex(is->pictq.mutex);
            ......
            if (frame_queue_nb_remaining(&is->pictq) > 1) {
                Frame *nextvp = frame_queue_peek_next(&is->pictq);
                duration = vp_duration(is, vp, nextvp);
                if(!is->step && (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration){
                    is->frame_drops_late++;
                    frame_queue_next(&is->pictq);
                    goto retry;
                }
            }
            ...

            frame_queue_next(&is->pictq);
            is->force_refresh = 1;

            if (is->step && !is->paused)
                stream_toggle_pause(is);
        }
display:
        /* display picture */
        if (!display_disable && is->force_refresh && is->show_mode == SHOW_MODE_VIDEO && is->pictq.rindex_shown)
        //Render + display get frames in FrameQueue
            video_display(is);
    }
    is->force_refresh = 0;
 ...
        }
    }
}

Pause processing, fast forward, fast backward

//A structure that records time during frame playback
//The variables in this are in real seconds, so it is necessary to convert them during storage, such as ST - > time_base, this is time_ Best use of base
typedef struct Clock {
    double pts;           //pts of frame
    double pts_drift;     //Difference between pts and current time
    double last_updated;  //Current storage last time
    double speed;        //The playback rate is generally 1, or fast forward playback
    int serial;           //Sequence number of the queue where the frame is located
    int paused;           //Pause flag
    int *queue_serial;    /* pointer to the current packet queue serial, used for obsolete clock detection */
} Clock;

//initialize variable
static void init_clock(Clock *c, int *queue_serial)
{
    c->speed = 1.0;
    c->paused = 0;
    c->queue_serial = queue_serial;
    set_clock(c, NAN, -1);
}

//Assign pts and current time to Clock
static void set_clock(Clock *c, double pts, int serial)
{
   //Gets the current time converted to seconds
    double time = av_gettime_relative() / 1000000.0;
    set_clock_at(c, pts, serial, time);
}

//Assign current time and pts
//Each frame of audio and video will call set_clock_at recording time
//The audio will return the callback time of the hardware playback audio_ callback_ Assign, time
//The video will be updated during playback_ video_ pts update time, where pts is in stream_ component_ open->video_ It has been converted into seconds in thread
static void set_clock_at(Clock *c, double pts, int serial, double time)
{
    c->pts = pts;
    c->last_updated = time;
    c->pts_drift = c->pts - time;
    c->serial = serial;
}

//Get the time information of the frame during playback
//If it is a pause, it is the current pts
//If you fast forward or rewind, you need to get the time more accurately
//You need to multiply the difference between the time at this time (that is, the time of pressing the keyboard) and the time recorded last time by the rate and add it to pts, which is more accurate
static double get_clock(Clock *c)
{
    if (*c->queue_serial != c->serial)
        return NAN;
    if (c->paused) {
    //If it is a pause, it is the current pts
        return c->pts;
    } else {
    //If you fast forward or rewind, you need to get the time more accurately
    //You need to multiply the difference between the time at this time (that is, the time of pressing the keyboard) and the time recorded last time by the rate and add it to pts, which is more accurate
        double time = av_gettime_relative() / 1000000.0;
        return c->pts_drift + time - (time - c->last_updated) * (1.0 - c->speed);
    }
}

//The synchronization time assigns slave to Clock *c
//The name is interesting enough. I have to use the word slave
static void sync_clock_to_slave(Clock *c, Clock *slave)
{
    double clock = get_clock(c);
    double slave_clock = get_clock(slave);
    if (!isnan(slave_clock) && (isnan(clock) || fabs(clock - slave_clock) > AV_NOSYNC_THRESHOLD))
        set_clock(c, slave_clock, slave->serial);
}

//Get clock
//master means exactly here. Hahaha, hahaha. At first, I didn't understand why it was named like this
static double get_master_clock(VideoState *is)
{
    double val;

    switch (get_master_sync_type(is)) {
        case AV_SYNC_VIDEO_MASTER:
            val = get_clock(&is->vidclk);
            break;
        case AV_SYNC_AUDIO_MASTER:
            val = get_clock(&is->audclk);
            break;
        default:
            val = get_clock(&is->extclk);
            break;
    }
    return val;
}

//Get synchronization time type
//ffplay is based on AV_SYNC_AUDIO_MASTER based
static int get_master_sync_type(VideoState *is) {
    if (is->av_sync_type == AV_SYNC_VIDEO_MASTER) {
        if (is->video_st)
            return AV_SYNC_VIDEO_MASTER;
        else
            return AV_SYNC_AUDIO_MASTER;
    } else if (is->av_sync_type == AV_SYNC_AUDIO_MASTER) {
        if (is->audio_st)
            return AV_SYNC_AUDIO_MASTER;
        else
            return AV_SYNC_EXTERNAL_CLOCK;
    } else {
        return AV_SYNC_EXTERNAL_CLOCK;
    }
}

//The video updates the Clock, and the pts of the current frame will be recorded for each frame playback, and assigned to is - > extclk
static void update_video_pts(VideoState *is, double pts, int64_t pos, int serial) {
    /* update current video pts */
    set_clock(&is->vidclk, pts, serial);
    sync_clock_to_slave(&is->extclk, &is->vidclk);
}

//The audio updates the Clock, and the pts of the current frame will be recorded for each frame playback, and assigned to is - > extclk
//audio_callback_time is the time passed to the callback function when the hardware plays audio
static void sdl_audio_callback(void *opaque, Uint8 *stream, int len)
{
    ....
    /* Let's assume the audio driver that is used by SDL has two periods. */
    if (!isnan(is->audio_clock)) {
        set_clock_at(&is->audclk, is->audio_clock - (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec, is->audio_clock_serial, audio_callback_time / 1000000.0);
        sync_clock_to_slave(&is->extclk, &is->audclk);
    }
}

Suspend processing

static void toggle_pause(VideoState *is)
{
    stream_toggle_pause(is);
    is->step = 0;
}

static void stream_toggle_pause(VideoState *is)
{
//av_gettime_relative gets the current time, and the return value is in milliseconds
    if (is->paused) {
        is->frame_timer += av_gettime_relative() / 1000000.0 - is->vidclk.last_updated;
        if (is->read_pause_return != AVERROR(ENOSYS)) {
            is->vidclk.paused = 0;
        }
        //The pause is subject to the video time
        //Fast forward and fast backward are subject to the audio time
        set_clock(&is->vidclk, get_clock(&is->vidclk), is->vidclk.serial);
    }
    //Set pts or pts of temporary clock - > extclk_ Drive is assigned to is - > extclk
    set_clock(&is->extclk, get_clock(&is->extclk), is->extclk.serial);
    //If the previous step is pause, the pause will be canceled here. Otherwise, it will be set as pause
    is->paused = is->audclk.paused = is->vidclk.paused = is->extclk.paused = !is->paused;
}