FFmpeg中与视频解码相关知识简介

1、引言

FFmpeg是一个非常流行的开源跨平台多媒体解决方案。它可以用于编码、解码、转换和流处理各种音频和视频格式。本文将介绍FFmpeg中与视频解码相关的知识。

2、视频解码器

FFmpeg的视频解码器支持多种视频编码格式,包括H.264、MPEG-4、AVC、VP9等等。

2.1 FFmpeg视频解码器

FFmpeg视频解码器可以使用avcodec_find_decoder()函数进行查找,例如:

AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
    fprintf(stderr, "无法找到解码器\n");
    return -1;
}

上述代码使用AV_CODEC_ID_H264参数查找H.264解码器。如果找不到指定的解码器,会返回NULL。

2.2 解码视频数据包

在视频解码过程中,需要读取视频数据流,并将其分解成多个压缩帧,然后将每个压缩帧解码为原始视频帧。可以使用av_read_frame()函数读取视频数据流,例如:

AVPacket packet;
​
while (av_read_frame(pFormatCtx, &packet) >= 0) {
​
    // 如果是视频流
    if (packet.stream_index == videoStreamIndex) {
​
        // 发送数据包给解码器
        if (avcodec_send_packet(pCodecCtx, &packet) != 0) {
            fprintf(stderr, "无法向解码器发送数据包\n");
            break;
        }
​
        // 解码帧数据
        while (avcodec_receive_frame(pCodecCtx, pFrame) == 0) {
            // 处理解码后的视频帧
            // ...
        }
    }
​
    av_packet_unref(&packet);
}

上述代码使用av_read_frame()函数读取视频数据流中的每个数据包,然后判断是否为视频流。如果是视频流,则使用avcodec_send_packet()将数据包发送给解码器进行解码,然后使用avcodec_receive_frame()函数接收解码后的视频帧。

2.3 视频帧格式转换

在解码过程中,可以选择将视频帧解码为不同的格式。可以使用SwsContext结构体和sws_scale()函数将视频帧转换为所需的输出格式。例如:

struct SwsContext *pSwsContext = sws_getContext(
    pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
    output_width, output_height, AV_PIX_FMT_RGB24,
    SWS_BILINEAR, NULL, NULL, NULL);
​
sws_scale(pSwsContext, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, &buffer, NULL);

上述代码创建了一个转换上下文,并使用sws_scale()函数将源视频帧转换为RGB格式,存储在缓冲区中。

2.4 清理资源

在完成视频解码后,需要清理使用过的资源,以避免内存泄漏。可以使用av_free()、av_frame_free()、avcodec_close()等函数来释放相应的资源。例如:

av_free(buffer);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);

以上是一些基本的FFmpeg视频解码器相关知识及代码示例,具体实现可能会因应用场景不同而有所变化。

3、解码音频和视频

FFmpeg可以同时解码音频和视频。解码音频和视频可以使用不同的函数,例如avcodec_decode_audio4()和avcodec_decode_video2()函数。

解码音频和视频是FFmpeg的基本功能之一

3.1 解码音频

要解码音频,需要打开音频文件,获取音频流并解码。

以下是解码音频的代码示例:

#include 
#include 
#include 
#include 
​
#include 
#include 
#include 
#include 
​
int main(int argc, char** argv) {
    // 打开输入音频文件
    AVFormatContext* format_ctx = NULL;
    if (avformat_open_input(&format_ctx, "input.mp3", NULL, NULL) != 0) {
        fprintf(stderr, "Failed to open input file\n");
        return -1;
    }
​
    // 获取音频流信息
    if (avformat_find_stream_info(format_ctx, NULL) < 0) {
        fprintf(stderr, "Failed to find stream information\n");
        avformat_close_input(&format_ctx);
        return -1;
    }
​
    // 查找第一个音频流
    int audio_stream_idx = -1;
    for (int i = 0; i < format_ctx->nb_streams; i++) {
        if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            audio_stream_idx = i;
            break;
        }
    }
    if (audio_stream_idx == -1) {
        fprintf(stderr, "No audio stream found\n");
        avformat_close_input(&format_ctx);
        return -1;
    }
​
    // 获取音频解码器
    AVCodec* codec = avcodec_find_decoder(format_ctx->streams[audio_stream_idx]->codecpar->codec_id);
    if (codec == NULL) {
        fprintf(stderr, "Unsupported codec\n");
        avformat_close_input(&format_ctx);
        return -1;
    }
​
    // 打开音频解码器
    AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
    if (avcodec_parameters_to_context(codec_ctx, format_ctx->streams[audio_stream_idx]->codecpar) < 0) {
        fprintf(stderr, "Failed to copy codec parameters to decoder context\n");
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }
    if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
        fprintf(stderr, "Failed to open codec\n");
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }
​
    // 初始化音频缓冲区和FIFO
    int frame_size = codec_ctx->frame_size;
    AVAudioFifo* fifo = av_audio_fifo_alloc(codec_ctx->sample_fmt, codec_ctx->channels, frame_size);
    if (fifo == NULL) {
        fprintf(stderr, "Failed to allocate audio FIFO\n");
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }
    uint8_t** buffer = NULL;
    if (av_samples_alloc_array_and_samples(&buffer, NULL, codec_ctx->channels, frame_size, codec_ctx->sample_fmt, 0) < 0) {
        fprintf(stderr, "Failed to allocate audio buffer\n");
        av_audio_fifo_free(fifo);
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }
​
    // 解码音频流
    AVPacket packet;
    av_init_packet(&packet);
    while (av_read_frame(format_ctx, &packet) == 0) {
        if (packet.stream_index == audio_stream_idx) {
            int ret = avcodec_send_packet(codec_ctx, &packet);
            if (ret < 0) {
                fprintf(stderr, "Error sending a packet for decoding\n");
                break;
            }
            while (ret >= 0) {
                AVFrame* frame = av_frame_alloc();
                ret = avcodec_receive_frame(codec_ctx, frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    av_frame_free(&frame);
                    break;
                } else if (ret < 0) {
                    fprintf(stderr, "Error during decoding\n");
                    av_frame_free(&frame);
                    break;
                }
                int samples = av_samples_get_buffer_size(NULL, codec_ctx->channels, frame->nb_samples, codec_ctx->sample_fmt, 0);
                if (samples <= 0) {
                    av_frame_free(&frame);
                    continue;
                }
                memcpy(buffer[0], frame->data[0], samples);
                if (codec_ctx->channels > 1) {
                    memcpy(buffer[1], frame->data[1], samples / 2);
                }
                av_audio_fifo_write(fifo, (void**)buffer, frame->nb_samples);
                av_frame_free(&frame);
            }
        }
        av_packet_unref(&packet);
    }
    av_packet_unref(&packet);
​
    // 关闭音频解码器和输入文件,释放缓冲区和FIFO
    avcodec_free_context(&codec_ctx);
    avformat_close_input(&format_ctx);
    av_freep(&buffer[0]);
    av_freep(&buffer);
    av_audio_fifo_free(fifo);
​
    return 0;
}

3.2 解码视频

要解码视频,需要打开视频文件,获取视频流并解码。以下是解码视频的代码示例:

#include 
#include 
#include 
#include 
​
#include 
#include 
#include 
​
int main(int argc, char** argv) {
    // 打开输入视频文件
    AVFormatContext* format_ctx = NULL;
    if (avformat_open_input(&format_ctx, "input.mp4", NULL, NULL) != 0) {
        fprintf(stderr, "Failed to open input file\n");
        return -1;
    }
​
    // 获取视频流信息
    if (avformat_find_stream_info(format_ctx, NULL) < 0) {
        fprintf(stderr, "Failed to find stream information\n");
        avformat_close_input(&format_ctx);
        return -1;
    }
​
    // 查找第一个视频流
    int video_stream_idx = -1;
    for (int i = 0; i < format_ctx->nb_streams; i++) {
        if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_idx = i;
            break;
        }
    }
    if (video_stream_idx == -1) {
        fprintf(stderr, "No video stream found\n");
        avformat_close_input(&format_ctx);
        return -1;
    }
​
    // 获取视频解码器
    AVCodec* codec = avcodec_find_decoder(format_ctx->streams[video_stream_idx]->codecpar->codec_id);
    if (codec == NULL) {
        fprintf(stderr, "Unsupported codec\n");
        avformat_close_input(&format_ctx);
        return -1;
    }
​
    // 打开视频解码器
    AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
    if (avcodec_parameters_to_context(codec_ctx, format_ctx->streams[video_stream_idx]->codecpar) < 0) {
        fprintf(stderr, "Failed to copy codec parameters to decoder context\n");
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }
    if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
        fprintf(stderr, "Failed to open codec\n");
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }
​
    // 初始化视频缓冲区和转换器
    AVFrame* frame = av_frame_alloc();
    AVFrame* rgb_frame = av_frame_alloc();
    uint8_t* buffer = NULL;
    int num_bytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codec_ctx->width, codec_ctx->height, 1);
    if (num_bytes <= 0) {
        fprintf(stderr, "Failed to get image buffer size\n");
        av_frame_free(&rgb_frame);
        av_frame_free(&frame);
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }
    buffer = (uint8_t*)av_malloc(num_bytes * sizeof(uint8_t));
    if (buffer == NULL) {
        fprintf(stderr, "Failed to allocate image buffer\n");
        av_frame_free(&rgb_frame);
        av_frame_free(&frame);
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }
    av_image_fill_arrays(rgb_frame->data, rgb_frame->linesize, buffer, AV_PIX_FMT_RGB24, codec_ctx->width, codec_ctx->height, 1);
    struct SwsContext* sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL);
    if (sws_ctx == NULL) {
        fprintf(stderr, "Failed to initialize color conversion context\n");
        av_freep(&buffer);
        av_frame_free(&rgb_frame);
        av_frame_free(&frame);
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }
​
    // 解码视频流
    AVPacket packet;
    av_init_packet(&packet);
    while (av_read_frame(format_ctx, &packet) == 0) {
        if (packet.stream_index == video_stream_idx) {
            int ret = avcodec_send_packet(codec_ctx, &packet);
            if (ret < 0) {
                fprintf(stderr, "Error sending a packet for decoding\n");
                break;
            }
            while (ret >= 0) {
                ret = avcodec_receive_frame(codec_ctx, frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    break;
                } else if (ret < 0) {
                    fprintf(stderr, "Error during decoding\n");
                    break;
                }
                sws_scale(sws_ctx, frame->data, frame->linesize, 0, codec_ctx->height, rgb_frame->data, rgb_frame->linesize);
                // 在此处可以将RGB图像显示或保存到文件中
            }
        }
        av_packet_unref(&packet);
    }
    av_packet_unref(&packet);
​
    // 关闭视频解码器和输入文件,释放缓冲区和转换器
    sws_freeContext(sws_ctx);
    av_freep(&buffer);
    av_frame_free(&rgb_frame);
    av_frame_free(&frame);
    avcodec_free_context(&codec_ctx);
    avformat_close_input(&format_ctx);
​
    return 0;
}

上述代码示例展示了如何使用FFmpeg解码音频和视频。在实际应用中,还需要根据具体的需求对解码后的音频和视频进行处理、保存或播放等操作。

4、帧率控制

帧率(Frame Rate)是指每秒钟播放的画面数,通常用FPS(Frames Per Second)表示。视频的质量和流畅度与帧率有很大关系,一般来说,帧率越高,视频就越流畅,但同时会占用更多的储存空间和计算资源。

【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发

【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~

FFmpeg中与视频解码相关知识简介_第1张图片

以下是一些常见的帧率控制方法:

4.1 -r选项

这是最简单的帧率控制方法,使用该选项可以指定输出视频流的帧率。

例如,下面的命令将输入视频的帧率设置为25fps,并将其写入输出文件output.mp4中:

ffmpeg -i input.mp4 -r 25 output.mp4

4.2 setpts过滤器

使用setpts过滤器可以调整视频帧之间的时间间隔。

例如,下面的命令将视频的帧率提高到60fps,并将其输出到output.mp4中:

ffmpeg -i input.mp4 -filter:v "setpts=0.5*PTS" -r 60 output.mp4

这里通过将每个视频帧的显示时间减半来增加视频帧率。

4.3 fps过滤器

使用fps过滤器可以从视频流中提取特定的帧,并按照指定的速率生成新的视频流。

例如,下面的命令将原始视频转换为10fps的gif图像:

ffmpeg -i input.mp4 -vf fps=10 output.gif

4.4 selectsetpts过滤器的组合

使用selectsetpts过滤器的组合可以将视频帧按照指定的时间间隔截取成图片,并保持在相同的帧率下。

例如,下面的命令将输入视频每10秒钟截取一张图像,并将其保存为output_%03d.jpg:

ffmpeg -i input.mp4 -vf "select=not(mod(n\,300)),setpts=N/FRAME_RATE/TB" output_%03d.jpg

这里mod(n\,300)表示只选择每300个帧中的一个帧,setpts=N/FRAME_RATE/TB则是根据帧率重新计算每个帧的显示时间。

总的来说,FFmpeg提供了丰富的帧率控制功能,可以根据不同场景灵活使用,从而达到更精细的视频处理效果。

4.5 实现帧率控制

在FFmpeg中,我们通过调整输入视频的PTS(Presentation Time Stamp,展示时间戳)和DTS(Decoding Time Stamp,解码时间戳)来实现帧率控制,例如:

4.5.1 指定输出视频的帧率

使用FPS过滤器可以让你指定输出视频的帧率,比如:

ffmpeg -i input.mp4 -c:v libx264 -filter:v fps=30 output.mp4

此命令将input.mp4转换为H.264编码格式,并将输出视频帧率设置为30。

4.5.2 修改输入视频的帧率

如果原始视频的帧率过高或过低,需要对其进行修改以适合当前场景。使用setpts过滤器可以修改输入视频的PTS,从而改变帧率,比如:

# 将25fps的视频转换为30fps
ffmpeg -i input.mp4 -filter:v "setpts=1.25*PTS" output.mp4
​
# 将60fps的视频转换为30fps
ffmpeg -i input.mp4 -filter:v "setpts=2.0*PTS" output.mp4

此命令将input.mp4中的每一帧展示时间戳加倍或减半,从而达到修改帧率的目的。

需要注意的是,修改帧率可能会导致视频画面变形或失真,因此必须谨慎使用。

5、解码器输出格式

在FFmpeg中,解码器是将音视频数据从压缩格式解码成原始格式的组件之一。

FFmpeg解码器输出格式与输入格式相关,不同的输入格式对应着不同的解码器,不同的解码器输出格式也略有不同。一般来说,FFmpeg解码器可以输出以下几种常用的视频和音频格式:

5.1 视频格式

5.1.1 YUV格式

YUV是一种色彩空间,也是一种常见的视频格式。YUV图像由亮度(Y)和色度(U、V)三个分量构成。其中,Y表示亮度,U、V表示色度。在视频编码和处理过程中,经常使用YUV420P、YUV422P和YUV444P等子格式。

将视频转换为YUV格式可以使用如下命令:

ffmpeg -i input.mp4 -pix_fmt yuv420p output.yuv

此命令将input.mp4文件转换为YUV420P格式,并保存到output.yuv文件中。

5.1.2 RGB格式

RGB是一种常见的颜色空间,也是一种常见的视频格式。RGB图像由红、绿、蓝三个基本色光组成。在视频处理过程中,经常使用RGB24、RGBA等子格式。

将视频转换为RGB格式可以使用如下命令:

ffmpeg -i input.mp4 -pix_fmt rgb24 output.rgb

此命令将input.mp4文件转换为RGB24格式,并保存到output.rgb文件中。

5.1.3 H.264/H.265格式

H.264/H.265是一种常见的视频压缩格式,可在保证高清晰度的同时,大幅降低视频文件大小。在FFmpeg中,可以通过指定编码器和参数来将视频编码为H.264/H.265格式。

将视频转换为H.264格式可以使用如下命令:

ffmpeg -i input.mp4 -c:v libx264 output.mp4

此命令将input.mp4文件转换为H.264编码格式,并保存到output.mp4文件中。

将视频转换为H.265格式可以使用如下命令:

ffmpeg -i input.mp4 -c:v libx265 output.mp4

此命令将input.mp4文件转换为H.265编码格式,并保存到output.mp4文件中。

需要注意的是,不同的视频格式对应着不同的输出质量、文件大小和解码速度等特点,使用时需要根据实际需求进行选择和调整。

5.2 音频格式

5.2.1 PCM格式

PCM是一种无损压缩格式,音质非常好,但文件大小较大。在FFmpeg中,可以将音频文件转换为PCM格式,也可以从PCM格式转换为其他格式。

将音频转换为PCM格式可以使用如下命令:

ffmpeg -i input.mp3 -acodec pcm_s16le output.wav

此命令将input.mp3文件转换为PCM格式,并保存到output.wav文件中。

将PCM格式转换为其他格式可以使用如下命令:

ffmpeg -f s16le -ar 44100 -ac 2 -i input.pcm -acodec libmp3lame output.mp3

此命令将input.pcm文件转换为MP3格式,并保存到output.mp3文件中。

5.2.2 MP3格式

MP3是一种有损压缩格式,可有效减少音频文件大小,但会损失一定的音质。在FFmpeg中,可以将音频文件转换为MP3格式,也可以从MP3格式转换为其他格式。

将音频转换为MP3格式可以使用如下命令:

将MP3格式转换为其他格式可以使用如下命令:

ffmpeg -i input.mp3 -acodec pcm_s16le output.wav

此命令将input.wav文件转换为MP3格式,并保存到output.mp3文件中。

将MP3格式转换为其他格式可以使用如下命令:

ffmpeg -i input.mp3 -acodec pcm_s16le output.wav

此命令将input.mp3文件转换为PCM格式,并保存到output.wav文件中。

5.2.3 AAC格式

AAC是一种常见的有损压缩音频格式,相比MP3更先进,可以达到更高的音质压缩比。在FFmpeg中,可以将音频文件转换为AAC格式,也可以从AAC格式转换为其他格式。

将音频转换为AAC格式可以使用如下命令:

ffmpeg -i input.wav -c:a libfdk_aac -b:a 128k output.aac

此命令将input.wav文件转换为AAC格式,并保存到output.aac文件中。

将AAC格式转换为其他格式可以使用如下命令:

ffmpeg -i input.m4a -acodec pcm_s16le output.wav

此命令将input.m4a文件转换为PCM格式,并保存到output.wav文件中。

需要注意的是,不同的音频格式对应着不同的输出质量、文件大小和解码速度等特点,使用时需要根据实际需求进行选择和调整。

6、视频编码格式

在FFmpeg中,视频编码器是将原始视频数据(如YUV或RGB)压缩成各种视频编码格式的组件之一。下面介绍一些常用的视频编码格式。

6.1 H.264/AVC

H.264是一种广泛应用的视频压缩标准,也被称为AVC(Advanced Video Coding)。它是一种有损压缩技术,可以在保持高质量的情况下大幅度减小视频文件的大小。H.264在互联网视频传输和储存方面使用广泛,适用于HTML5等多种平台。同时,H.264还支持一个可扩展框架,即SVC(Scalable Video Coding),可以根据网络带宽自动调整视频的清晰度,以提供最佳的观看体验。

在FFmpeg中,使用libx264编码器可以将原始视频数据编码为H.264格式的视频,例如:

ffmpeg -i input.mp4 -c:v libx264 -preset veryslow -crf 18 output.mp4

这里指定了输出文件为H.264格式,并使用了-c:v libx264选项以选择libx264编码器。另外,-preset veryslow表示使用最慢的编码速度来达到更好的视频质量,-crf 18则是指定视频质量(数值越小代表质量越高)。

6.2 H.265/HEVC

HEVC(High Efficiency Video Coding)也被称为H.265,是一种新的视频压缩标准,与H.264相比,H.265具有更高的压缩效率和更低的比特率,可以处理更高分辨率和更复杂的视频内容。由于H.265需要更强的计算能力进行编解码,因此需要更高的硬件要求。

在FFmpeg中,使用libx265编码器可以将原始视频数据编码为HEVC/H.265格式的视频,例如:

ffmpeg -i input.mp4 -c:v libx265 -preset veryslow -crf 18 output.mp4

这里与H.264例子类似,只是使用了-c:v libx265选项以选择libx265编码器。

6.3 VP9

VP9是一种由Google开发的新型视频压缩标准。VP9压缩技术在保证高质量的情况下可以将视频文件大小压缩到原来的一半或更少。VP9主要用于WebM格式视频中,适用于各种平台。

在FFmpeg中,使用libvpx-vp9编码器可以将原始视频数据编码为VP9格式的视频,例如:

ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 32 -b:v 0 output.webm

这里指定了输出文件为VP9格式,并使用了-c:v libvpx-vp9选项以选择libvpx-vp9编码器。另外,-crf 32表示视频质量(数值越大代表质量越低),-b:v 0则是指定比特率为0,使其自动适应目标质量。

6.4 AV1

AV1是一种由Alliance for Open Media(AOMedia)联盟开发的新型视频压缩标准。与H.265和VP9相比,AV1具有更高的压缩效率和更低的比特率,并且是完全开源的。AV1在逐渐得到广泛的应用,例如在YouTube、Netflix等大型视频网站上已经开始使用了AV1进行视频编码。

7、总结

总之,FFmpeg是一个非常强大的多媒体解决方案,可以用来处理各种音频和视频格式。在视频解码过程中,需要了解视频解码器、帧率控制、解码器输出格式、视频编码格式等相关知识,才能正确地解码视频数据。

你可能感兴趣的:(程序员,音视频开发,编程,ffmpeg,音视频,H264,YUV,C++)