基于QT ffmpeg多媒体播放器项目(一)

前言

FFmpeg 是一个开源的多媒体处理库,广泛用于音视频的编码、解码、转码、处理和流媒体传输等任务。

前言将详细讲解如何使用 FFmpeg 库进行编码和解码操作

1. 初始化 FFmpeg 库

在使用 FFmpeg 库之前,需要先初始化相关的模块。通常在程序的开始部分调用 av_register_all() 和 avformat_network_init() 函数。

extern "C" 
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
}
int main() 
{
    av_register_all();
    avformat_network_init();
    // 其他代码
return 0;
}

2. 打开输入文件(解码)

使用 avformat_open_input 函数打开输入文件。在这个过程中,你需要创建一个 AVFormatContext 结构体来存储文件格式的信息。

AVFormatContext *formatContext = NULL;

const char *inputFilePath = "input.mp4";

if (avformat_open_input(&formatContext, inputFilePath, NULL, NULL) != 0)

{

    fprintf(stderr, "无法打开输入文件\n");

return -1;

}

3. 获取输入文件的流信息(解码)

使用 avformat_find_stream_info 函数获取输入文件的流信息。这一步会填充 formatContext 中的流列表。

if (avformat_find_stream_info(formatContext, NULL) < 0)

{

    fprintf(stderr, "无法获取流信息\n");

return -1;

}

4. 查找视频流(解码)

遍历 formatContext 中的流列表,找到视频流。

视频流的 codec_type 通常为 AVMEDIA_TYPE_VIDEO

int videoStreamIndex = -1;
for (int i = 0; i < formatContext->nb_streams; i++)
{
    if(formatContext->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO)

    {
        videoStreamIndex = i;
        break;
    }
}

if (videoStreamIndex == -1)

{
    fprintf(stderr, "未找到视频流\n");
    return -1;
}

5. 获取编解码器参数(解码)

从找到的视频流中获取编解码器参数。

AVCodecParameters*codecParameters=formatContext->streams[videoStreamIndex]->codecpar;

6. 查找解码器(解码)

使用 avcodec_find_decoder 函数根据编解码器参数找到相应的解码器。

AVCodec*decoder=avcodec_find_decoder(codecParameters->codec_id);
if (!decoder)

{
    fprintf(stderr, "未找到解码器\n");
    return -1;
}

7. 创建编解码上下文(解码)

使用 avcodec_alloc_context3 函数创建一个编解码上下文,并使用 avcodec_parameters_to_context 函数将编解码器参数复制到编解码上下文中。

AVCodecContext *codecContext = avcodec_alloc_context3(decoder);
if (avcodec_parameters_to_context(codecContext, codecParameters) < 0) 
{
    fprintf(stderr, "无法将编解码器参数复制到编解码上下文中\n");
    return -1;
}

8. 打开解码器(解码)

使用 avcodec_open2 函数打开解码器。这一步会初始化编解码上下文。

if (avcodec_open2(codecContext, decoder, NULL) < 0)
{
    fprintf(stderr, "无法打开解码器\n");
    return -1;
}

9. 解码数据(解码)

读取视频数据包并解码。使用 av_read_frame 函数读取数据包,然后使用 avcodec_send_packet 和 avcodec_receive_frame 函数进行解码。

AVPacket packet;
AVFrame *frame = av_frame_alloc();
while (av_read_frame(formatContext, &packet) >= 0) 
{
    if (packet.stream_index == videoStreamIndex) 
    {
        if (avcodec_send_packet(codecContext, &packet) < 0) 
        {
            fprintf(stderr, "无法发送数据包到解码器\n");
            break;
        }
        while (avcodec_receive_frame(codecContext, frame) == 0) 
        {
            // 处理解码后的帧
            // 例如,显示帧或进行其他处理
        }
    }
    av_packet_unref(&packet);
}
av_frame_free(&frame);
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);

10. 打开输出文件(编码)

使用 avformat_alloc_output_context2 函数创建输出文件的格式上下文。

AVFormatContext *outputFormatContext = NULL;
const char *outputFilePath = "output.mp4";
avformat_alloc_output_context2(&outputFormatContext, NULL, NULL, outputFilePath);
if (!outputFormatContext)
{
    fprintf(stderr, "无法分配输出格式上下文\n");
    return -1;
}

11. 创建输出视频流(编码)

创建一个新的视频流,并设置其参数。通常需要设置视频的宽、高、帧率、比特率等参数。

AVStream *outStream = avformat_new_stream(outputFormatContext, NULL);
if (!outStream)
{
    fprintf(stderr, "无法创建新的视频流\n");
    return -1;
}
outStream->id = outputFormatContext->nb_streams - 1;
outStream->time_base = (AVRational){1, 25};

// 设置编码器参数
AVCodecContext *encoderContext = avcodec_alloc_context3(NULL);
if (!encoderContext)
{
    fprintf(stderr, "无法分配编码器上下文\n");
    return -1;
}

encoderContext->codec_id = AV_CODEC_ID_H264; // 选择一个编码器
encoderContext->bit_rate = 2000000; // 比特率
encoderContext->width = 1280; // 宽度
encoderContext->height = 720; // 高度
encoderContext->time_base = (AVRational){1, 25}; // 帧率
encoderContext->gop_size = 12; // GOP 大小
encoderContext->max_b_frames = 1; // B帧数
encoderContext->pix_fmt = AV_PIX_FMT_YUV420P; // 像素格式

avcodec_parameters_from_context(outStream->codecpar,encoderContext);

12. 查找编码器(编码)

使用 avcodec_find_encoder 函数查找合适的编码器。

AVCodec *encoder = avcodec_find_encoder(encoderContext->codec_id);
if (!encoder)
{
    fprintf(stderr, "未找到编码器\n");
    return -1;
}
encoderContext->codec_id = encoder->id;

13. 打开编码器(编码)

使用 avcodec_open2 函数打开编码器。

if (avcodec_open2(encoderContext, encoder, NULL) < 0)
{
    fprintf(stderr, "无法打开编码器\n");
    return -1;
}

14. 写入文件头(编码)

在实际编码之前,需要写入文件头信息。

if(avio_open(&outputFormatContext->pb,outputFilePath,AVIO_FLAG_WRITE) < 0)
{
    fprintf(stderr, "无法打开输出文件\n");
    return -1;
}
avformat_write_header(outputFormatContext, NULL);

15. 编码数据(编码)

从解码后的帧中读取数据并进行编码。使用 avcodec_send_frame 和 avcodec_receive_packet 函数进行编码。

AVPacket *encodePacket = av_packet_alloc();
while (av_read_frame(formatContext, &packet) >= 0) 
{

    if (packet.stream_index == videoStreamIndex) 
    {

        if (avcodec_send_packet(codecContext, &packet) < 0) 
        {
            fprintf(stderr, "无法发送数据包到解码器\n");
            break;
        }
        while (avcodec_receive_frame(codecContext, frame) == 0) 
        {
            // 处理解码后的帧
            // 例如,显示帧或进行其他处理
            if (avcodec_send_frame(encoderContext, frame) < 0) 
            {
                fprintf(stderr, "无法发送帧到编码器\n");
                break;
            }
            while(avcodec_receive_packet(encoderContext,encodePacket) == 0) 
            {
               // 将编码后的数据包写入输出文件
               encodePacket->stream_index = outStream->index;
               av_packet_rescale_ts(encodePacket, encoderContext->time_base, outStream->time_base);

               av_interleaved_write_frame(outputFormatContext, encodePacket);
               av_packet_unref(encodePacket);
            }
        }
    }
av_packet_unref(&packet);}

// 写入文件尾
av_write_trailer(outputFormatContext);
av_packet_free(&encodePacket);
av_frame_free(&frame);
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
avio_close(outputFormatContext->pb);
avformat_free_context(outputFormatContext);

continue...

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值