FFmpeg 音视频多线程读取合并输出示例

FFmpeg  在处理音视频时,会经常遇到在一个线程中处理不过来的情况,这里分线程读取和输出的示例。要注意的是

1)环形队列在加入数据时,需要调用av_packet_ref增加引用计数,否则旧对象里指针会被释放,后面用的时候例如调用av_interleaved_write_frame后 ff内部再释放 会崩溃。

2)在read_thread读取线程中,每次读取包都直接调用assign_timestamps()函数,将包的时间戳设置成系统当前时间,这里假如输入源的时间戳不可靠,不能用的话,可以直接这样不使用输入包的时间戳

3)大概意思表述清楚了,具体处理根据实际情况处理,例如合并文件读取包的时候,根据时间戳前后顺序或者进行音视频同步处理合并输出都可以,这里没有做,因为我的输入源是同步的,只要处理的快,就也是同步的,就没有加

完整代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h>

#define MAX_QUEUE_SIZE 100

typedef struct {
    AVPacket packet;
    int stream_index;
} PacketQueueItem;

typedef struct {
    PacketQueueItem queue[MAX_QUEUE_SIZE];
    int size;
    int front;
    int rear;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    int data_available;  // 新数据是否可用的标志
    pthread_cond_t *merge_cond;  // 合并线程条件变量
} PacketQueue;

typedef struct {
    AVFormatContext *format_ctx;
    int stream_index;
    PacketQueue *queue;
	int videoready;  // 是否读到视频的第一个I帧
    AVCodecContext *inp_codectx;//输入解码器
} InputContext;

typedef struct {
    InputContext *audio_input_ctx;
    InputContext *video_input_ctx;
} MergeContext;

PacketQueue* create_queue(pthread_cond_t *merge_cond) {
    PacketQueue *q = (PacketQueue*)malloc(sizeof(PacketQueue));
    q->size = 0;
    q->front = 0;
    q->rear = 0;
    pthread_mutex_init(&q->mutex, NULL);
    pthread_cond_init(&q->cond, NULL);
    q->data_available = 0;
    q->merge_cond = merge_cond;  // 将条件变量传递给队列
    return q;
}

void assign_timestamps(AVPacket *packet, AVRational time_base) {
    // 使用 av_gettime() 获取当前系统时间,然后将其转换为时间戳
    int64_t current_time = av_gettime();


    // 根据输入流的时间基进行时间戳的转换
    int64_t pts = av_rescale_q(current_time, AV_TIME_BASE_Q, time_base);
    
    // 设置 packet 的 pts 和 dts 字段
    packet->pts = pts;
    packet->dts = pts;


}


void enqueue_packet(PacketQueue *q, AVPacket *packet, int stream_index) {
    pthread_mutex_lock(&q->mutex);
    if (q->size >= MAX_QUEUE_SIZE) {
        // 队列已满
        pthread_mutex_unlock(&q->mutex);
        return;
    }

    int rear = q->rear;
    q->queue[rear].packet = *packet;
    av_packet_ref(&q->queue[rear].packet, packet);  // 创建新的引用 否则旧对象里指针会被释放,后面用的时候 ff内部再释放 会崩溃
    q->queue[rear].stream_index = stream_index;
    q->rear = (rear + 1) % MAX_QUEUE_SIZE;
    q->size++;

    q->data_available = 1;  // 设置数据可用标志

    // 只有在真正有数据加入队列时才唤醒合并线程
    if (q->size == 1) {
        pthread_cond_signal(&q->cond);
        // 唤醒合并线程
      //  pthread_cond_signal(q->merge_cond);
    }

    pthread_mutex_unlock(&q->mutex);
}

int dequeue_packet(PacketQueue *q, AVPacket *packet, int *stream_index) {
    pthread_mutex_lock(&q->mutex);

    // 重置数据可用标志
    if (q->size == 0) {
        q->data_available = 0;
    }

    while (q->size <= 0) {
        pthread_cond_wait(&q->cond, &q->mutex);
    }

    int front = q->front;
    av_packet_unref(packet);  // 释放旧的数据包 减少引用次数

    *packet = q->queue[front].packet;
    *stream_index = q->queue[front].stream_index;
    q->front = (front + 1) % MAX_QUEUE_SIZE;
    q->size--;

    pthread_mutex_unlock(&q->mutex);
    return 1;
}

void* read_thread(void *arg) {
    InputContext *input_ctx = (InputContext*)arg;

    AVPacket packet;
    av_init_packet(&packet);

    while (av_read_frame(input_ctx->format_ctx, &packet) >= 0) {

	    // 为 packet 赋新的时间戳
 		assign_timestamps(&packet, input_ctx->format_ctx->streams[0]->time_base);

		if (input_ctx->stream_index == 0)
		{
			if (input_ctx->videoready)
			{
				enqueue_packet(input_ctx->queue, &packet, input_ctx->stream_index);
			}
			else
			{
                //这里videoready原本打算 接收到视频I帧之后,才可以真正合并输出的,可以去掉这块的逻辑 
				AVFrame *inp_stream_video_frame = av_frame_alloc();
				int ret;
				//没有解码到视频I帧
				//直接丢掉
				ret = avcodec_send_packet(input_ctx->inp_codectx, &packet);
				while (ret >= 0) 
				{
					ret = avcodec_receive_frame(input_ctx->inp_codectx, inp_stream_video_frame);
					// 检查帧的类型
					if (inp_stream_video_frame->pict_type == AV_PICTURE_TYPE_I) 
					{
							// 这是I帧,可以处理
							// 将该帧写入输出文件,合并文件等操作
							input_ctx->videoready = 1;

					}
					else
					{
					}
					
				}
				av_frame_free(&inp_stream_video_frame);
				
				if (input_ctx->videoready)
				{
					enqueue_packet(input_ctx->queue, &packet, input_ctx->stream_index);
				}
				
			}
		}
		else
		{
			enqueue_packet(input_ctx->queue, &packet, input_ctx->stream_index);
		}

        av_packet_unref(&packet);
    }

    return NULL;
}

void* merge_thread_helper(void *arg) {
    MergeContext *merge_ctx = (MergeContext*)arg;

    PacketQueue *audio_queue = merge_ctx->audio_input_ctx->queue;
    PacketQueue *video_queue = merge_ctx->video_input_ctx->queue;

    int audio_stream_index, video_stream_index;

   	AVPacket *audio_packet = av_packet_alloc();
	AVPacket *video_packet = av_packet_alloc();

    while (1) {
        // pthread_mutex_lock(&audio_queue->mutex);
        // // 在合并文件线程中,只等待音频的信号
        // while (!audio_queue->data_available /*&& !video_queue->data_available*/) {
        //     pthread_cond_wait(audio_queue->merge_cond, &audio_queue->mutex);
        // }
        // pthread_mutex_unlock(&audio_queue->mutex);

        if (audio_queue->size > 0) {
            dequeue_packet(audio_queue, &audio_packet, &audio_stream_index);
            // 处理音频数据
        }

        if (video_queue->size > 0) {
            dequeue_packet(video_queue, &video_packet, &video_stream_index);
            // 处理视频数据

			if (video_packet && video_packet->data && video_packet->size > 0) 
			{
                // 处理视频数据
                video_packet->pts =av_rescale_q(av_gettime(),
                                            AV_TIME_BASE_Q,
                                            merge_ctx->oc->streams[video_stream_index]->time_base);
                video_packet->dts = video_packet->pts;
                video_packet->stream_index = video_stream_index;
			
			
				// AVPacket newpacket ;
				// newpacket = *video_packet;
				// 在这里添加你的处理逻辑
				ret =  av_interleaved_write_frame(merge_ctx->oc, video_packet);
				if (ret < 0)
				{

				}
			}
			else
			{
			}
        }

        // TODO: 处理音视频数据
    }
	
    if (audio_packet) {
		av_packet_free(&audio_packet);
		audio_packet = NULL;  // 将指针置为 NULL 避免重复释放
	}	
    if (video_packet) {
		av_packet_free(&video_packet);
		video_packet = NULL;  // 将指针置为 NULL 避免重复释放
	}	
    return NULL;
}

int main(int argc, char *argv[]) {
    if (argc < 4) {
        fprintf(stderr, "我的使用方法: %s <音频输入> <视频输入> <输出>\n", argv[0]);
        return 1;
    }

    av_register_all();

    AVCodecContext *inp_video_codecctx = NULL;
    AVCodecContext *inp_audio_codecctx = NULL;

	inp_video_codecctx = avcodec_alloc_context3(NULL);
	inp_audio_codecctx = avcodec_alloc_context3(NULL);

    InputContext audio_input_ctx, video_input_ctx;
    audio_input_ctx.format_ctx = avformat_alloc_context();
    video_input_ctx.format_ctx = avformat_alloc_context();

    if (avformat_open_input(&audio_input_ctx.format_ctx, argv[1], NULL, NULL) < 0 ||
        avformat_open_input(&video_input_ctx.format_ctx, argv[2], NULL, NULL) < 0) {
        fprintf(stderr, "打开输入文件时出错\n");
        return 1;
    }

    audio_input_ctx.stream_index = 0; // 设置所需的音频流索引
    video_input_ctx.stream_index = 0; // 设置所需的视频流索引
    audio_input_ctx.is_first_video_frame = 1;  // 标记第一个视频帧
	
    video_input_ctx.inp_codectx = inp_video_codecctx;
	audio_input_ctx.inp_codectx = inp_audio_codecctx;

    pthread_t audio_thread, video_thread, merge_thread;
    pthread_cond_t merge_cond = PTHREAD_COND_INITIALIZER;

    audio_input_ctx.queue = create_queue(&merge_cond);
    video_input_ctx.queue = create_queue(&merge_cond);
	video_input_ctx.videoready = 0;
	audio_input_ctx.videoready = 0;

    MergeContext merge_ctx;
    merge_ctx.audio_input_ctx = &audio_input_ctx;
    merge_ctx.video_input_ctx = &video_input_ctx;

    pthread_create(&audio_thread, NULL, read_thread, &audio_input_ctx);
    pthread_create(&video_thread, NULL, read_thread, &video_input_ctx);
    pthread_create(&merge_thread, NULL, merge_thread_helper, &merge_ctx);

    pthread_join(audio_thread, NULL);
    pthread_join(video_thread, NULL);
    pthread_join(merge_thread, NULL);


    // 清理代码

    free(input_ctx_video.queue);
	free(input_ctx_audio.queue);

    avcodec_free_context(&inp_video_codecctx);
	avcodec_free_context(&inp_audio_codecctx);
    

    return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值