Directorio de artículos
- 1.ff introducción al juego
- 2.Proceso marco de FFplay
- 3.Análisis de la estructura de datos
1.ff introducción al juego
ffplay es un reproductor proporcionado por el código fuente de FFmpeg, es un reproductor implementado por la API de FFmpeg y SDL y tiene importancia de referencia para el desarrollo secundario de reproductores posteriores, como ijkplayer de Bilibili.
2.Proceso marco de FFplay
3.Análisis de la estructura de datos
3.1 El director general de ffplay
typedef struct VideoState {
SDL_Thread *read_tid; // 读线程句柄
AVInputFormat *iformat; // 指向demuxer
int abort_request; // =1时请求退出播放
int force_refresh; // =1时需要刷新画面,请求立即刷新画面的意思
int paused; // =1时暂停,=0时播放
int last_paused; // 暂存“暂停”/“播放”状态
int queue_attachments_req;
int seek_req; // 标识一次seek请求
int seek_flags; // seek标志,诸如AVSEEK_FLAG_BYTE等
int64_t seek_pos; // 请求seek的目标位置(当前位置+增量)
int64_t seek_rel; // 本次seek的位置增量
int read_pause_return;
AVFormatContext *ic; // iformat的上下文
int realtime; // =1为实时流
Clock audclk; // 音频时钟
Clock vidclk; // 视频时钟
Clock extclk; // 外部时钟
FrameQueue pictq; // 视频Frame队列
FrameQueue subpq; // 字幕Frame队列
FrameQueue sampq; // 采样Frame队列
Decoder auddec; // 音频解码器
Decoder viddec; // 视频解码器
Decoder subdec; // 字幕解码器
int audio_stream ; // 音频流索引
int av_sync_type; // 音视频同步类型, 默认audio master
double audio_clock; // 当前音频帧的PTS+当前帧Duration
int audio_clock_serial; // 播放序列,seek可改变此值
// 以下4个参数 非audio master同步方式使用
double audio_diff_cum; // used for AV difference average computation
double audio_diff_avg_coef;
double audio_diff_threshold;
int audio_diff_avg_count;
// end
AVStream *audio_st; // 音频流
PacketQueue audioq; // 音频packet队列
int audio_hw_buf_size; // SDL音频缓冲区的大小(字节为单位)
// 指向待播放的一帧音频数据,指向的数据区将被拷入SDL音频缓冲区。若经过重采样则指向audio_buf1,
// 否则指向frame中的音频
uint8_t *audio_buf; // 指向需要重采样的数据
uint8_t *audio_buf1; // 指向重采样后的数据
unsigned int audio_buf_size; // 待播放的一帧音频数据(audio_buf指向)的大小
unsigned int audio_buf1_size; // 申请到的音频缓冲区audio_buf1的实际尺寸
int audio_buf_index; // 更新拷贝位置 当前音频帧中已拷入SDL音频缓冲区
// 的位置索引(指向第一个待拷贝字节)
// 当前音频帧中尚未拷入SDL音频缓冲区的数据量:
// audio_buf_size = audio_buf_index + audio_write_buf_size
int audio_write_buf_size;
int audio_volume; // 音量
int muted; // =1静音,=0则正常
struct AudioParams audio_src; // 音频frame的参数
#if CONFIG_AVFILTER
struct AudioParams audio_filter_src;
#endif
struct AudioParams audio_tgt; // SDL支持的音频参数,重采样转换:audio_src->audio_tgt
struct SwrContext *swr_ctx; // 音频重采样context
int frame_drops_early; // 丢弃视频packet计数
int frame_drops_late; // 丢弃视频frame计数
enum ShowMode {
SHOW_MODE_NONE = -1, // 无显示
SHOW_MODE_VIDEO = 0, // 显示视频
SHOW_MODE_WAVES, // 显示波浪,音频
SHOW_MODE_RDFT, // 自适应滤波器
SHOW_MODE_NB
} show_mode;
// 音频波形显示使用
int16_t sample_array[SAMPLE_ARRAY_SIZE]; // 采样数组
int sample_array_index; // 采样索引
int last_i_start; // 上一开始
RDFTContext *rdft; // 自适应滤波器上下文
int rdft_bits; // 自使用比特率
FFTSample *rdft_data; // 快速傅里叶采样
int xpos;
double last_vis_time;
SDL_Texture *vis_texture; // 音频Texture
SDL_Texture *sub_texture; // 字幕显示
SDL_Texture *vid_texture; // 视频显示
int subtitle_stream; // 字幕流索引
AVStream *subtitle_st; // 字幕流
PacketQueue subtitleq; // 字幕packet队列
double frame_timer; // 记录最后一帧播放的时刻
double frame_last_returned_time; // 上一次返回时间
double frame_last_filter_delay; // 上一个过滤器延时
int video_stream; // 视频流索引
AVStream *video_st; // 视频流
PacketQueue videoq; // 视频队列
double max_frame_duration; // 一帧最大间隔. above this, we consider the jump a timestamp discontinuity
struct SwsContext *img_convert_ctx; // 视频尺寸格式变换
struct SwsContext *sub_convert_ctx; // 字幕尺寸格式变换
int eof; // 是否读取结束
char *filename; // 文件名
int width, height, xleft, ytop; // 宽、高,x起始坐标,y起始坐标
int step; // =1 步进播放模式, =0 其他模式
#if CONFIG_AVFILTER
int vfilter_idx;
AVFilterContext *in_video_filter; // the first filter in the video chain
AVFilterContext *out_video_filter; // the last filter in the video chain
AVFilterContext *in_audio_filter; // the first filter in the audio chain
AVFilterContext *out_audio_filter; // the last filter in the audio chain
AVFilterGraph *agraph; // audio filter graph
#endif
// 保留最近的相应audio、video、subtitle流的steam index
int last_video_stream, last_audio_stream, last_subtitle_stream;
SDL_cond *continue_read_thread; // 当读取数据队列满了后进入休眠时,可以通过该condition唤醒读线程
} VideoState;
3.2 paquete de reloj struct Clock
typedef struct Clock {
double pts; // 时钟基础, 当前帧(待播放)显示时间戳,播放后,当前帧变成上一帧
// 当前pts与当前系统时钟的差值, audio、video对于该值是独立的
double pts_drift; // clock base minus time at which we updated the clock
// 当前时钟(如视频时钟)最后一次更新时间,也可称当前时钟时间
double last_updated; // 最后一次更新的系统时钟
double speed; // 时钟速度控制,用于控制播放速度
// 播放序列,所谓播放序列就是一段连续的播放动作,一个seek操作会启动一段新的播放序列
int serial; // clock is based on a packet with this serial
int paused; // = 1 说明是暂停状态
// 指向packet_serial
int *queue_serial; /* pointer to the current packet queue serial, used for obsolete clock detection */
} Clock;
3.3 Diseño de estructura de datos de datos de cola de paquetes
typedef struct MyAVPacketList {
AVPacket pkt; //解封装后的数据
struct MyAVPacketList *next; //下一个节点
int serial; //播放序列
} MyAVPacketList;
typedef struct PacketQueue {
MyAVPacketList *first_pkt, *last_pkt; // 队首,队尾指针
int nb_packets; // 包数量,也就是队列元素数量
int size; // 队列所有元素的数据大小总和
int64_t duration; // 队列所有元素的数据播放持续时间
int abort_request; // 用户退出请求标志
int serial; // 播放序列号,和MyAVPacketList的serial作用相同,但改变的时序稍微有点不同
SDL_mutex *mutex; // 用于维持PacketQueue的多线程安全(SDL_mutex可以按pthread_mutex_t理解)
SDL_cond *cond; // 用于读、写线程相互通知(SDL_cond可以按pthread_cond_t理解)
} PacketQueue;
Según el código, podemos ver que la estructura MyAVPacketList es un nodo de un AVPacket y PacketQueue se usa para administrar la cola de este grupo de nodos.
A continuación, echemos un vistazo a algunas funciones que operan esta cola.
3.3.1 paquete_cola_init
static int packet_queue_init(PacketQueue *q)
{
memset(q, 0, sizeof(PacketQueue));
q->mutex = SDL_CreateMutex();
if (!q->mutex) {
av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
return AVERROR(ENOMEM);
}
q->cond = SDL_CreateCond();
if (!q->cond) {
av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
return AVERROR(ENOMEM);
}
q->abort_request = 1;
return 0;
}
Se puede ver en el código fuente que esta función se utiliza para crear una nueva cola y crear bloqueos y variables de condición para ella.
Esta función nos permite crear directamente colas AVPacket para vídeo, audio y subtítulos.
3.3.2 destrucción_cola_paquete
static void packet_queue_destroy(PacketQueue *q)
{
packet_queue_flush(q); //先清除所有的节点
SDL_DestroyMutex(q->mutex);
SDL_DestroyCond(q->cond);
}
Esta función es lo opuesto a paquete_queue_init. Se utiliza para liberar la cola de paquetes. Se llama a la función paquete_queue_flush. Esta función libera cada nodo en la cola y luego libera el bloqueo y la variable de condición. Puede que tenga una pregunta aquí. ¿Por qué liberar PacketQueue? ? Debido a que esta variable está almacenada en la pila, podemos verla mirando la variable PacketQueue en VideoState.
3.3.3 paquete_queue_flush
static void packet_queue_flush(PacketQueue *q)
{
MyAVPacketList *pkt, *pkt1;
SDL_LockMutex(q->mutex);
for (pkt = q->first_pkt; pkt; pkt = pkt1) {
pkt1 = pkt->next;
av_packet_unref(&pkt->pkt);
av_freep(&pkt);
}
q->last_pkt = NULL;
q->first_pkt = NULL;
q->nb_packets = 0;
q->size = 0;
q->duration = 0;
SDL_UnlockMutex(q->mutex);
}
Esta función es muy simple: atraviesa la cola y libera todos los nodos de la cola.
3.3.4 inicio_cola_paquete
static void packet_queue_start(PacketQueue *q)
{
SDL_LockMutex(q->mutex);
q->abort_request = 0;
packet_queue_put_private(q, &flush_pkt); //这里放入了一个flush_pkt
SDL_UnlockMutex(q->mutex);
}
Esta función es para iniciar la cola, el paso es cambiar abort_request a 0. Era 1 cuando lo inicializamos al principio.
Luego llame a pack_queue_put_private para colocar un flush_pkt en la cola, que se utiliza como una línea divisoria entre dos piezas de datos no continuos. Al insertar flush_pkt, se iniciará la operación de incremento en serie en 1 y se iniciará el decodificador para borrar su propio caché, avcodec_flush_buffers. , en la función decoder_decode_frame Reflejado
3.3.5 paquete_cola_abortar
static void packet_queue_abort(PacketQueue *q)
{
SDL_LockMutex(q->mutex);
q->abort_request = 1; // 请求退出
SDL_CondSignal(q->cond); //释放一个条件信号
SDL_UnlockMutex(q->mutex);
}
Esta función es para terminar la cola, es decir, cambiará la variable en PacketQueue, la establecerá en 1 y solicitará salir. Aquí se llama a SDL_CondSignal para evitar que otros subprocesos se bloqueen mientras esperan. Esta función se llamará bajo la función decoder_abort .
3.3.6 paquete_cola_put
static int packet_queue_put(PacketQueue *q, AVPacket *pkt)
{
int ret;
SDL_LockMutex(q->mutex);
ret = packet_queue_put_private(q, pkt);//主要实现
SDL_UnlockMutex(q->mutex);
if (pkt != &flush_pkt && ret < 0)
av_packet_unref(pkt); //放入失败,释放AVPacket
return ret;
}
Esta función llamará a package_queue_put_private. Si la inserción falla, se liberará el AVPacket.
3.3.7 paquete_cola_obtener
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial)
{
MyAVPacketList *pkt1;
int ret;
SDL_LockMutex(q->mutex); // 加锁
for (;;) {
if (q->abort_request) {
ret = -1;
break;
}
pkt1 = q->first_pkt; //MyAVPacketList *pkt1; 从队头拿数据
if (pkt1) {
//队列中有数据
q->first_pkt = pkt1->next; //队头移到第二个节点
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--; //节点数减1
q->size -= pkt1->pkt.size + sizeof(*pkt1); //cache大小扣除一个节点
q->duration -= pkt1->pkt.duration; //总时长扣除一个节点
//返回AVPacket,这里发生一次AVPacket结构体拷贝,AVPacket的data只拷贝了指针
*pkt = pkt1->pkt;
if (serial) //如果需要输出serial,把serial输出
*serial = pkt1->serial;
av_free(pkt1); //释放节点内存,只是释放节点,而不是释放AVPacket
ret = 1;
break;
} else if (!block) {
//队列中没有数据,且非阻塞调用
ret = 0;
break;
} else {
//队列中没有数据,且阻塞调用
//这里没有break。for循环的另一个作用是在条件变量满足后重复上述代码取出节点
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex); // 释放锁
return ret;
}
Esta función obtiene pkt. Del código podemos ver que atravesará la cola, si no hay datos en la cola, se bloqueará y esperará hasta que lleguen los datos antes de salir.
3.3.8 paquete_cola_put_nullpaquete
static int packet_queue_put_nullpacket(PacketQueue *q, int stream_index)
{
AVPacket pkt1, *pkt = &pkt1;
av_init_packet(pkt);
pkt->data = NULL;
pkt->size = 0;
pkt->stream_index = stream_index;
return packet_queue_put(q, pkt);
}
Insertar un paquete vacío significa que los datos del archivo se han leído y el decodificador debe leer todos los fotogramas.
3.4 Diseño de estructura de datos de cola de cuadros
typedef struct Frame {
AVFrame *frame; // 指向数据帧
AVSubtitle sub; // 用于字幕
int serial; // 帧序列,在seek的操作时serial会变化
double pts; // 时间戳,单位为秒
double duration; // 该帧持续时间,单位为秒
int64_t pos; // 该帧在输入文件中的字节位置
int width; // 图像宽度
int height; // 图像高读
int format; // 对于图像为(enum AVPixelFormat),
// 对于声音则为(enum AVSampleFormat)
AVRational sar; // 图像的宽高比(16:9,4:3...),如果未知或未指定则为0/1
int uploaded; // 用来记录该帧是否已经显示过?
int flip_v; // =1则垂直翻转, = 0则正常播放
} Frame;
/* 这是一个循环队列,windex是指其中的首元素,rindex是指其中的尾部元素. */
typedef struct FrameQueue {
Frame queue[FRAME_QUEUE_SIZE]; // FRAME_QUEUE_SIZE 最大size, 数字太大时会占用大量的内存,需要注意该值的设置
int rindex; // 读索引。待播放时读取此帧进行播放,播放后此帧成为上一帧
int windex; // 写索引
int size; // 当前总帧数
int max_size; // 可存储最大帧数
int keep_last; // = 1说明要在队列里面保持最后一帧的数据不释放,只在销毁队列的时候才将其真正释放
int rindex_shown; // 初始化为0,配合keep_last=1使用
SDL_mutex *mutex; // 互斥量
SDL_cond *cond; // 条件变量
PacketQueue *pktq; // 数据包缓冲队列
} FrameQueue;
La estructura Frame encapsula los datos AVFrame decodificados. AVSubtitle se utiliza para almacenar fotogramas de subtítulos. Frame está diseñado para audio, vídeo y subtítulos. FrameQueue utiliza una cola circular. Esta cola sin bloqueo puede ser mejor para mejorar el rendimiento
.
3.4.1 frame_queue_init
static int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last)
{
int i;
memset(f, 0, sizeof(FrameQueue));
if (!(f->mutex = SDL_CreateMutex())) {
av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
return AVERROR(ENOMEM);
}
if (!(f->cond = SDL_CreateCond())) {
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++)
if (!(f->queue[i].frame = av_frame_alloc())) // 分配AVFrame结构体
return AVERROR(ENOMEM);
return 0;
}
Inicialice la cola, cree bloqueos y variables de condición, y asígneles avframe
Nota: max_size no se puede establecer demasiado grande para fotogramas de vídeo. Suponiendo que el formato sea YUV420p, 1,5 * 1080 * 720 = 1.166.400 bytes = 1,112 Mb para 1080p
3.4.2 frame_queue_destory
static void frame_queue_destory(FrameQueue *f)
{
int i;
for (i = 0; i < f->max_size; i++) {
Frame *vp = &f->queue[i];
// 释放对vp->frame中的数据缓冲区的引用,注意不是释放frame对象本身
frame_queue_unref_item(vp);
// 释放vp->frame对象
av_frame_free(&vp->frame);
}
SDL_DestroyMutex(f->mutex);
SDL_DestroyCond(f->cond);
}
static void frame_queue_unref_item(Frame *vp)
{
av_frame_unref(vp->frame); /* 释放数据 */
avsubtitle_free(&vp->sub);
}
3.4.3 frame_queue_peek_writable
// 获取可写指针
static Frame *frame_queue_peek_writable(FrameQueue *f)
{
/* wait until we have space to put a new frame */
SDL_LockMutex(f->mutex);
while (f->size >= f->max_size &&
!f->pktq->abort_request) {
/* 检查是否需要退出 */
SDL_CondWait(f->cond, f->mutex);
}
SDL_UnlockMutex(f->mutex);
if (f->pktq->abort_request) /* 检查是不是要退出 */
return NULL;
return &f->queue[f->windex];
}
Esta función es para obtener el puntero de Marco para la siguiente operación de escritura. Si la cola está llena, regresará vacía.
3.4.4 frame_queue_push
// 更新写指针
static void frame_queue_push(FrameQueue *f)
{
if (++f->windex == f->max_size)
f->windex = 0;
SDL_LockMutex(f->mutex);
f->size++;
SDL_CondSignal(f->cond); // 当_readable在等待时则可以唤醒
SDL_UnlockMutex(f->mutex);
}
Esta función actualiza el índice de escritura.
3.4.5 imagen_cola
static int queue_picture(VideoState *is, AVFrame *src_frame, double pts,double duration, int64_t pos, int serial)
{
Frame *vp;
#if defined(DEBUG_SYNC)
printf("frame_type=%c pts=%0.3f\n",
av_get_picture_type_char(src_frame->pict_type), pts);
#endif
if (!(vp = frame_queue_peek_writable(&is->pictq))) // 检测队列是否有可写空间,如果没有就会阻塞这边
return -1; // 请求退出则返回-1
// 执行到这步说已经获取到了可写入的Frame
vp->sar = src_frame->sample_aspect_ratio;
vp->uploaded = 0;
vp->width = src_frame->width;
vp->height = src_frame->height;
vp->format = src_frame->format;
vp->pts = pts;
vp->duration = duration;
vp->pos = pos;
vp->serial = serial;
set_default_window_size(vp->width, vp->height, vp->sar);
av_frame_move_ref(vp->frame, src_frame); // 将src中所有数据转移到dst中,并复位src。
frame_queue_push(&is->pictq); // 更新写索引位置
return 0;
}
Esta función se usa esencialmente para escribir cuadros: obtiene la posición del cuadro grabable a través de la función frame_queue_peek_writable, luego la inicializa y otras operaciones, y finalmente llama a frame_queue_push para apuntar el índice de escritura a la siguiente posición.
3.4.6 frame_queue_peek_readable
static Frame *frame_queue_peek_readable(FrameQueue *f)
{
/* wait until we have a readable a new frame */
SDL_LockMutex(f->mutex);
while (f->size - f->rindex_shown <= 0 &&
!f->pktq->abort_request) {
SDL_CondWait(f->cond, f->mutex);
}
SDL_UnlockMutex(f->mutex);
if (f->pktq->abort_request)
return NULL;
return &f->queue[(f->rindex + f->rindex_shown) % f->max_size];
}
Obtenga un objeto de la cola y, si la cola está vacía, espere a que se coloque la cola de escritura.
3.4.7 frame_queue_siguiente
static void frame_queue_next(FrameQueue *f)
{
if (f->keep_last && !f->rindex_shown) {
f->rindex_shown = 1; // 第一次进来没有更新,对应的frame就没有释放
return;
}
frame_queue_unref_item(&f->queue[f->rindex]);
if (++f->rindex == f->max_size)
f->rindex = 0;
SDL_LockMutex(f->mutex);
f->size--;
SDL_CondSignal(f->cond);
SDL_UnlockMutex(f->mutex);
}
Esta función actualiza el índice de escritura y lectura, es decir, frame_queue_peek_readable obtiene el marco y luego elimina las referencias del marco actual.
3.4.8 frame_queue_peek_last/frame_queue_peek/frame_queue_peek_next
/* 获取队列当前Frame, 在调用该函数前先调用frame_queue_nb_remaining确保有frame可读 */
static Frame *frame_queue_peek(FrameQueue *f)
{
return &f->queue[(f->rindex + f->rindex_shown) % f->max_size];
}
/* 获取当前Frame的下一Frame, 此时要确保queue里面至少有2个Frame */
// 不管你什么时候调用,返回来肯定不是 NULL
static Frame *frame_queue_peek_next(FrameQueue *f)
{
return &f->queue[(f->rindex + f->rindex_shown + 1) % f->max_size];
}
/* 获取last Frame:
* 当rindex_shown=0时,和frame_queue_peek效果一样
* 当rindex_shown=1时,读取的是已经显示过的frame
*/
static Frame *frame_queue_peek_last(FrameQueue *f)
{
return &f->queue[f->rindex]; // 这时候才有意义
}
3.4.9 frame_queue_nb_remaining
static int frame_queue_nb_remaining(FrameQueue *f)
{
return f->size - f->rindex_shown; // 注意这里为什么要减去f->rindex_shown
}
Obtenga el fotograma no mostrado, reste rindex_shown porque si está configurado para conservar el fotograma anterior, entonces el fotograma no mostrado será -1
3.5 Parámetros de audio AudioParams
typedef struct AudioParams {
int freq; // 采样率
int channels; // 通道数
int64_t channel_layout; // 通道布局,比如2.1声道,5.1声道等
enum AVSampleFormat fmt; // 音频采样格式,比如AV_SAMPLE_FMT_S16表示为有符号16bit深度,交错排列模式。
int frame_size; // 一个采样单元占用的字节数(比如2通道时,则左右通道各采样一次合成一个采样单元)
int bytes_per_sec; // 一秒时间的字节数,比如采样率48Khz,2 channel,16bit,则一秒48000*2*16/8=192000
} AudioParams;
3.6 Estructura del decodificador del decodificador
typedef struct Decoder {
AVPacket pkt;
PacketQueue *queue; // 数据包队列
AVCodecContext *avctx; // 解码器上下文
int pkt_serial; // 包序列
int finished; // =0,解码器处于工作状态;=非0,解码器处于空闲状态
int packet_pending; // =0,解码器处于异常状态,需要考虑重置解码器;=1,解码器处于正常状态
SDL_cond *empty_queue_cond; // 检查到packet队列空时发送 signal缓存read_thread读取数据
int64_t start_pts; // 初始化时是stream的start time
AVRational start_pts_tb; // 初始化时是stream的time_base
int64_t next_pts; // 记录最近一次解码后的frame的pts,当解出来的部分帧没有有效的pts时则使用next_pts进行推算
AVRational next_pts_tb; // next_pts的单位
SDL_Thread *decoder_tid; // 线程句柄
} Decoder;