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