[Combate FFmpeg] Cola FFplay PacketQueue

Enlace del artículo: https://blog.csdn.net/weixin_42764231/article/details/127545066

1. Encapsule su propia estructura de datos y almacene lo que desee almacenar.

MyAVPacketList encapsula el AVPacket en ffmpeg, y el número de serie interno se utiliza para identificar si el paquete es la secuencia de reproducción actual. De lo contrario, se descartará.

typedef struct MyAVPacketList {
    
    
        AVPacket *pkt;   //demux后的数据包
        int serial;      //播放序列
} MyAVPacketList;

PacketQueue es una estructura utilizada para almacenar MyAVPacketList. La función paquete_queue_put_private() almacenará los paquetes entrantes externos en PacketQueuet. AVFifo es una estructura que almacena datos en bytes. Las versiones anteriores de ffplay usaban el puntero de cola "MyAVPacketList *first_pkt, *last_pkt" para el almacenamiento de datos. Las transmisiones de audio, vídeo y subtítulos tienen su propio PacketQueue independiente.

typedef struct PacketQueue{
    
    
	    AVFifo *pkt_list;  //数据存储缓存区域
	    int nb_packets;   //队列内一共有多少元素
	    int size;         //队列内所有元素的大小
	    int64_t duration; //队列里的元素一共可以播放多长时间
	    int abort_request; //视频播放是否退出
	    int serial;        //播放序列来自于MyAVPacketList的播放序列
	    SDL_mutex *mutex;  //线程锁,保证多线程时数据正确
	    SDL_cond *cond;    //用于读写线程互相通知
}PacketQueue;
struct AVFifo {
    
    
    uint8_t *buffer;  //字节流Buffer

    size_t elem_size, nb_elems;
    size_t offset_r, offset_w;
    // distinguishes the ambiguous situation offset_r == offset_w
    int    is_empty;

    unsigned int flags;
    size_t       auto_grow_limit;
};

2. Haz lo que quieras con tu propia estructura de datos.

Antes de que los soldados y los caballos muevan la comida y el pasto, primero use paquete_queue_init() para inicializar el paquete.

static int packet_queue_init(PacketQueue *q)
{
    
    
    memset(q, 0, sizeof(PacketQueue));
    q->pkt_list = av_fifo_alloc2(1, sizeof(MyAVPacketList), AV_FIFO_FLAG_AUTO_GROW); //申请pkt_list的空间
    if (!q->pkt_list)
        return AVERROR(ENOMEM);
    q->mutex = SDL_CreateMutex(); //申请mutex的空间
    if (!q->mutex) {
    
    
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    q->cond = SDL_CreateCond(); //申请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;
}

El juramento hecho por el ejército antes de salir, PacketQueue está habilitado, abort_request se establece en 1 durante la inicialización y en 0 al inicio. Solo cuando es 0 se puede jugar normalmente, y cuando es 1 no se puede jugar. Al mismo tiempo, el número de serie se incrementará en 1 para restablecer la secuencia de reproducción.

static void packet_queue_start(PacketQueue *q)
{
    
    
    SDL_LockMutex(q->mutex);
    q->abort_request = 0;
    q->serial++;
    SDL_UnlockMutex(q->mutex);
}

Almacenamiento de granos, paquete_queue_put encapsula un paquete de datos en MyAVPacketList y lo almacena en PacketQueue llamando a paquete_queue_put_private.

static int packet_queue_put(PacketQueue *q, AVPacket *pkt)
{
    
    
    AVPacket *pkt1;
    int ret;

    pkt1 = av_packet_alloc();
    if (!pkt1) {
    
    
        av_packet_unref(pkt);
        return -1;
    }
    av_packet_move_ref(pkt1, pkt);

    SDL_LockMutex(q->mutex);
    ret = packet_queue_put_private(q, pkt1);
    SDL_UnlockMutex(q->mutex);

    if (ret < 0)
        av_packet_free(&pkt1);

    return ret;
}

pack_queue_put_private hace lo siguiente para almacenar datos

1. Calcular el número de serie. marcas de serie cuando los datos en este nodo son. Generalmente, el número de serie del nuevo nodo es el mismo que el del nodo anterior.

Lo mismo, pero después de que se produce una operación de búsqueda, se agregará un Flush_pkt a la cola, por lo que el número de serie aumentará en 1 y el número de serie de los nodos posteriores será uno más grande que el anterior para distinguir diferentes secuencias de reproducción.

2. Realice la entrada del nodo en la cola a través de av_fifo_write.

3. Operación de atributos de cola. Actualice la cantidad de nodos en la cola, la cantidad de bytes ocupados (incluido el tamaño de AVPacket.data) y la duración. Puede controlar el tamaño de la cola PacketQueue limitando los valores máximos de tamaño, duración y nb_packets.

static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
{
    
    
    MyAVPacketList pkt1;
    int ret;

    if (q->abort_request)
       return -1;

    pkt1.pkt = pkt;
    pkt1.serial = q->serial;

    ret = av_fifo_write(q->pkt_list, &pkt1, 1);
    if (ret < 0)
        return ret;
    q->nb_packets++;
    q->size += pkt1.pkt->size + sizeof(pkt1);
    q->duration += pkt1.pkt->duration;
    /* XXX: should duplicate packet data in DV case */
    SDL_CondSignal(q->cond);
    return 0;
}

av_packet_move_ref() copiará completamente src a dst. Una vez completada la copia, los datos de src se borrarán mediante la función get_packet_defaults(). Debido a que dst y src tienen sus propios espacios de direcciones, borrar src no afectará a dst.

void av_packet_move_ref(AVPacket *dst, AVPacket *src)
{
    
    
    *dst = *src;
    get_packet_defaults(src);
}

Reponga comida y pasto mientras camina, solo cuando esté lleno podrá trabajar. El objetivo de paquete_queue_get es obtener AvPacket, principalmente av_fifo_read() lee el marco almacenado en pkt_list. Después de que la lectura sea exitosa, nb_packet se reduce en uno. Tenga en cuenta que la longitud que se restará del tamaño no es solo el tamaño de los datos en pkt1, pero también el tamaño del propio pkt1. . Posteriormente, solo necesita copiar el marco recuperado av_packet_move_ref() a la dirección del espacio que se devolverá.

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;
        }
        
        if (av_fifo_read(q->pkt_list, &pkt1, 1) >= 0) {
    
     // 表示队列中有数据,成功读取
            q->nb_packets--;
            q->size -= pkt1.pkt->size + sizeof(pkt1);
            q->duration -= pkt1.pkt->duration;
            av_packet_move_ref(pkt, pkt1.pkt); //将pkt返回出去
            if (serial) //如果需要输出serial,把serial传出
                *serial = pkt1.serial;
            av_packet_free(&pkt1.pkt);
            ret = 1;
            break;
        } else if (!block) {
    
    
            //队列中无数据,且为⾮阻塞调⽤
            ret = 0;
            break;
        } else {
    
    
            //此处不break。等待满足条件变量,重复上述步骤取数据        
            SDL_CondWait(q->cond, q->mutex);
        }
    }
    SDL_UnlockMutex(q->mutex);
    return ret;
}

Coloque paquetes vacíos en PacketQueue La función paquete_queue_put_nullpacket en github ffplay.c No. 2022/10/27 necesita pasar un paquete y luego almacenarlo en la cola PackeQueue. Mire los cuatro lugares donde se llama a esta función. Tres de ellos establecerán el valor de eof en 1 para indicar el final de la reproducción. El otro es sobre la llamada de visualización de imágenes de mp3. Después de la modificación aquí, no corresponde al nombre, por lo que el autor de ffmpeg todavía tiene algunas dudas sobre cómo modificar esta función, la función anterior se publicará a continuación.

static int packet_queue_put_nullpacket(PacketQueue *q, AVPacket *pkt, int stream_index)
{
    
    
    pkt->stream_index = stream_index;
    return packet_queue_put(q, pkt);
}

El paquete_queue_put_nullpacket original realmente insertará un paquete vacío

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);
}

Después de la batalla y preparándose para huir, primero limpie los suministros. En paquete_queue_flush, primero verifique si hay algún paquete en la cola. Si hay alguno, saque todos los paquetes y libérelos. Una vez completada la liberación, inicializa los parámetros de la cola PacketQueue. Utilizado principalmente en:

1. Borre PacketQueue al salir de la reproducción.

2. Después de la operación de búsqueda, los datos del nodo previamente almacenados en caché en PacketQueue deben borrarse para poder insertar nuevos datos del nodo.

static void packet_queue_flush(PacketQueue *q)
{
    
    
    MyAVPacketList pkt1;

    SDL_LockMutex(q->mutex);
    while (av_fifo_read(q->pkt_list, &pkt1, 1) >= 0)
        av_packet_free(&pkt1.pkt);
    q->nb_packets = 0;
    q->size = 0;
    q->duration = 0;
    q->serial++;
    SDL_UnlockMutex(q->mutex);
}

pack_queue_destroy() destruirá los recursos internos de PacketQueue y limpiará mutex y cond para evitar pérdidas de memoria.

static void packet_queue_destroy(PacketQueue *q)
{
    
    
    packet_queue_flush(q);
    av_fifo_freep2(&q->pkt_list);
    SDL_DestroyMutex(q->mutex);
    SDL_DestroyCond(q->cond);
}

3. Resumen

1.Gestión de memoria PacketQueue:

PacketQueue mantendrá el contenido de MyAVPackeList, llamará a av_malloc dentro de la función paquete_queue_put_private) malloc durante la transferencia y free durante la obtención (llame a av_packet_free dentro de la función paquete_queue_get).

La memoria AVPacket se divide en dos espacios:

1. La memoria de la estructura AVPacket, esta parte de la memoria coexistirá con MyAVPacketList.

2. La memoria a la que apunta el campo AVPacket debe liberarse mediante la función av_packet_unref.

2.Cambios de serie:

Cada vez que se inserta un Flush_pkt, el número de serie aumentará en 1.

3.Ideas de diseño de PacketQueue:

  1. Diseñe una cola segura de subprocesos múltiples, guarde AVPacket y cuente el tamaño de los datos almacenados en caché en la cola. (Estos datos estadísticos se utilizarán para establecer posteriormente la cantidad de datos que se almacenarán en caché)
  2. El concepto de serie se introduce para distinguir si los paquetes de datos anteriores y posteriores son continuos y se utiliza principalmente en operaciones de búsqueda.
  3. Se diseñan dos tipos especiales de paquetes: Flush_pkt y nullpkt (similar al modelo de eventos utilizado en la programación multiproceso: colocar eventos de descarga y eventos nulos en la cola).
  >>> 音视频开发 视频教程: https://ke.qq.com/course/3202131?flowToken=1031864 
  >>> 音视频开发学习资料、教学视频,免费分享有需要的可以自行添加学习交流群 739729163 领取

Supongo que te gusta

Origin blog.csdn.net/weixin_52622200/article/details/131662284
Recomendado
Clasificación