音视频开发—FFmpeg 音频重采样详解

音频重采样(audio resampling)是指改变音频信号的采样率的过程。采样率(sample rate)是指每秒钟采集的音频样本数,通常以赫兹(Hz)或每秒样本数(samples per second)表示。例如,CD音频的标准采样率是44.1kHz,表示每秒钟采集44,100个样本。

文章目录

什么时候会用到重采样

  1. 设备兼容性

    不同的音频设备可能支持不同的采样率。例如,某些音频接口或播放设备可能只支持特定的采样率。在这种情况下,需要将音频文件重采样到兼容的采样率。

  2. 节省存储空间

    高采样率的音频文件占用更多的存储空间。如果需要节省存储空间或带宽,可以将音频文件重采样到较低的采样率。

  3. 音频质量调整

    有时候需要在高质量和低质量之间进行平衡。例如,音频工程师可能会将录音采样率设置得很高,以捕捉更多的细节,但在混音或发行时,可能会选择稍低的采样率以便于处理和分发。

  4. 多媒体项目

    在多媒体项目中,不同来源的音频文件可能具有不同的采样率。为了保持项目的一致性,通常需要对音频文件进行重采样,使它们具有相同的采样率。

  5. 音频处理和分析

    某些音频处理和分析工具对输入的采样率有特定的要求。在使用这些工具之前,可能需要对音频进行重采样。

重采样的技术

重采样涉及插值算法,例如:

  1. 线性插值

    是最简单的一种插值方法,但可能会引入较多的失真。

  2. 多项式插值(如三次样条插值)

    能提供更高的精度,但计算复杂度较高。

  3. 窗函数插值(如Lanczos窗)

    常用于高质量音频重采样,能够有效减少伪影和失真。

通过这些算法,重采样过程可以在不同的采样率之间平滑过渡,尽量保持原音频信号的质量。

使用FFmpeg命令行对MP3文件进行重采样

一个名为input.mp3的MP3文件,并且你想将其采样率更改为48kHz(48000Hz),可以使用以下命令:

ffmpeg -i input.mp3 -ar 48000 output.mp3

参数说明

  • -i input.mp3:指定输入文件。
  • -ar 48000:指定新的采样率,这里是48000Hz。
  • output.mp3:指定输出文件名。

更详细的命令

如果你还想保留原始的比特率(bitrate)和声道数(channels),可以使用更多参数:

ffmpeg -i input.mp3 -ar 48000 -ab 192k -ac 2 output.mp3
  • -ab 192k:指定音频比特率为192kbps。
  • -ac 2:指定音频通道数为2(立体声)。

FFmpeg 对MP3文件重采样代码实现

流程图

关键步骤:

对MP3文件进行解码

具体流程如下:

打开输入文件 : 调用 avformat_open_input 函数打开输入文件,并获得格式上下文 fmt_ctx

读取流信息 : 调用 avformat_find_stream_info 函数读取文件的流信息,以便后续处理。

查找最佳音频流 : 调用 av_find_best_stream 函数查找输入文件中的最佳音频流,并返回其索引 audio_stream_index 和对应的解码器 codec

分配解码器上下文 : 调用 avcodec_alloc_context3 函数为找到的解码器分配解码器上下文 codec_ctx

将流的参数复制到解码器上下文 : 调用 avcodec_parameters_to_context 函数将输入文件中音频流的参数复制到解码器上下文中。

打开解码器 : 调用 avcodec_open2 函数打开解码器,准备解码音频数据。

代码实现:

int initialize_decoder(const char *input_filename, AVFormatContext **fmt_ctx, AVCodecContext **codec_ctx, int *audio_stream_index)
{
    AVCodec *codec = NULL;

    if (avformat_open_input(fmt_ctx, input_filename, NULL, NULL) < 0)
    {
        fprintf(stderr, "Could not open input file\n");
        return -1;
    }

    if (avformat_find_stream_info(*fmt_ctx, NULL) < 0)
    {
        fprintf(stderr, "Could not find stream information\n");
        return -1;
    }

    *audio_stream_index = av_find_best_stream(*fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0);
    if (*audio_stream_index < 0)
    {
        fprintf(stderr, "Could not find audio stream in input file\n");
        return -1;
    }

    *codec_ctx = avcodec_alloc_context3(codec);
    if (!(*codec_ctx))
    {
        fprintf(stderr, "Could not allocate audio codec context\n");
        return -1;
    }

    if (avcodec_parameters_to_context(*codec_ctx, (*fmt_ctx)->streams[*audio_stream_index]->codecpar) < 0)
    {
        fprintf(stderr, "Could not copy codec parameters to codec context\n");
        return -1;
    }

    if (avcodec_open2(*codec_ctx, codec, NULL) < 0)
    {
        fprintf(stderr, "Could not open codec\n");
        return -1;
    }


    return 0; // 成功
}
初始化重采样参数

具体流程如下

代码实现

int initialize_resampler(SwrContext **swr_ctx, AVCodecContext *codec_ctx, AVFrame **swr_frame)
{
    *swr_ctx = swr_alloc_set_opts(NULL,                      // ctx
                                  AV_CH_LAYOUT_STEREO,       // 输出的channel 的布局
                                  AV_SAMPLE_FMT_FLTP,        // 输出的采样格式
                                  48000,                     // 输出的采样率
                                  codec_ctx->channel_layout, // 输入的channel布局
                                  codec_ctx->sample_fmt,     // 输入的采样格式
                                  codec_ctx->sample_rate,    // 输入的采样率
                                  0, NULL);
    if (!(*swr_ctx))
    {
        fprintf(stderr, "Could not allocate resampler context\n");
        return -1;
    }
    if (swr_init(*swr_ctx) < 0)
    {
        fprintf(stderr, "Could not initialize the resampling context\n");
        swr_free(swr_ctx);
        return -1;
    }
    *swr_frame = av_frame_alloc();
    if (!(*swr_frame))
    {
        fprintf(stderr, "Could not allocate resampled frame\n");
        swr_free(swr_ctx);
        return -1;
    }

    return 0; // 成功
}
初始化编码器

大体流程如下:

设置输出文件格式和路径 : 调用 av_guess_format 函数猜测输出文件的格式,并根据输出文件名设置格式。

分配输出格式的上下文 : 调用 avformat_alloc_output_context2 函数为输出文件分配格式上下文。

创建输出流 : 调用 avformat_new_stream 函数为输出文件创建一个新的音频流。

查找编码器 : 调用 avcodec_find_encoder 函数查找MP3编码器。

创建编码器上下文 : 调用 avcodec_alloc_context3 函数为编码器分配上下文。

设置编码器参数: 设置编码器的比特率、采样格式、采样率和声道布局等参数。

打开编码器 : 调用 avcodec_open2 函数打开编码器。

给输出流设置编码器参数 : 调用 avcodec_parameters_from_context 函数将编码器的参数复制到输出流。

打开输出文件 : 如果输出格式不支持直接输出到文件,则调用 avio_open 函数打开输出文件。

写入文件头部 : 调用 avformat_write_header 函数写入输出文件的头部信息。

初始化输出数据包 : 调用 av_packet_alloc 函数初始化一个数据包,用于存储编码后的音频数据。

代码实现

int initialize_encoder(const char *output_filename, AVFormatContext **out_fmt_ctx, AVCodecContext **enc_ctx, AVStream **out_stream,AVPacket **outpack)
{
    AVOutputFormat *out_fmt = NULL;
    AVCodec *encoder = NULL;
    AVCodecContext *encoder_ctx = NULL;
    AVStream *stream = NULL;
    // 设置输出文件的格式与路径
    out_fmt = av_guess_format(NULL, output_filename, NULL);
    if (!out_fmt)
    {
        fprintf(stderr, "could not guess file format\n");
        return -1;
    }
    // 打开输出格式的上下文
    if (avformat_alloc_output_context2(out_fmt_ctx, out_fmt, NULL, output_filename) < 0)
    {
        fprintf(stderr, "could not create output context\n");
        return -1;
    }
    // 创建输出流
    stream = avformat_new_stream(*out_fmt_ctx, NULL);
    if (!stream)
    {
        fprintf(stderr, "could not create output stream\n");
        return -1;
    }
    // 查找编码器
    encoder = avcodec_find_encoder(AV_CODEC_ID_MP3);
    if (!encoder)
    {
        fprintf(stderr, "Codec not found\n");
        return -1;
    }
    // 创建编码器上下文
    encoder_ctx = avcodec_alloc_context3(encoder);
    if (!encoder_ctx)
    {
        fprintf(stderr, "Could not allocate audio codec context\n");
        return -1;
    }

    // 设置编码器参数
    encoder_ctx->bit_rate = 192000;                    // 比特率为192kbps
    encoder_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;      // 采样格式
    encoder_ctx->sample_rate = 48000;                  // 采样率
    encoder_ctx->channel_layout = AV_CH_LAYOUT_STEREO; // 双声道布局
    encoder_ctx->channels = 2;

    // 打开编码器
    if (avcodec_open2(encoder_ctx, encoder, NULL) < 0)
    {
        fprintf(stderr, "Could not open codec\n");
        avcodec_free_context(&encoder_ctx);
        return -1;
    }
    // 给输出流设置编码器
    if (avcodec_parameters_from_context(stream->codecpar, encoder_ctx) < 0)
    {
        fprintf(stderr, "Failed to copy encoder parameters to output stream\n");
        avcodec_free_context(&encoder_ctx);
        return -1;
    }
    // 打开输出文件
    if (!((*out_fmt_ctx)->oformat->flags & AVFMT_NOFILE))
    {
        if (avio_open(&(*out_fmt_ctx)->pb, output_filename, AVIO_FLAG_WRITE) < 0)
        {
            fprintf(stderr, "Could not open output file\n");
            return -1;
        }
    }
    // 写入文件头部
    if (avformat_write_header((*out_fmt_ctx), NULL) < 0)
    {
        fprintf(stderr, "Error occurred when opening output file\n");
        return -1;
    }
    *enc_ctx = encoder_ctx;
    *out_stream = stream;
    //初始化输出的数据包
    *outpack = av_packet_alloc();
    return 0; // 成功
}
音频帧转换

需要转换的帧要与编码器的参数注意对应,否则编码器无法编码

重点是swr_convert_frame

swr_convert_frame 是 FFmpeg 中用于将一个音频帧从一种格式转换为另一种格式的函数。它使用 SwrContext(重采样上下文)来执行转换,包括采样率、通道布局和采样格式的转换。

函数定义

int swr_convert_frame(SwrContext *swr_ctx, AVFrame *out_frame, const AVFrame *in_frame);

参数说明

  • swr_ctx:指向 SwrContext 的指针,包含重采样所需的上下文信息。这个上下文在之前需要通过 swr_alloc_set_optsswr_init 等函数进行初始化。
  • out_frame:指向目标 AVFrame 的指针,存储转换后的音频数据。这个 AVFrame 需要预先分配并设置好其参数,例如采样率、通道布局和采样格式等。
  • in_frame:指向源 AVFrame 的指针,包含需要转换的音频数据。

返回值

函数返回一个整数,成功时返回0,失败时返回负数。

使用步骤

  1. 初始化 SwrContext : 使用 swr_alloc_set_optsswr_init 函数初始化重采样上下文,设置输入和输出的采样率、通道布局和采样格式等。

  2. 分配并设置 AVFrame : 分配并设置输入和输出 AVFrame,并确保 out_frame 的参数与 SwrContext 的输出参数一致。

  3. 调用 swr_convert_frame : 调用 swr_convert_frame 函数进行音频数据的转换。

  4. 处理转换后的音频数据 : 转换完成后,可以使用 out_frame 中的数据进行后续处理。

     				// 确保有足够的缓冲区来容纳转换后的数据
                     swr_frame->channel_layout = AV_CH_LAYOUT_STEREO;
                     swr_frame->format = AV_SAMPLE_FMT_FLTP;
                     swr_frame->sample_rate = 48000;
                     swr_frame->nb_samples = encodec_ctx->frame_size;  //与编码器帧大小保持一致
    
                     // 分配缓冲区
                     if (av_frame_get_buffer(swr_frame, 0) < 0)
                     {
                         fprintf(stderr, "Could not allocate output frame samples\n");
                         av_frame_free(&swr_frame);
                         return -1;
                     }
    
                     // 执行重采样
                     if (swr_convert_frame(swr_ctx, swr_frame, frame) < 0)
                     {
                         fprintf(stderr, "Error while converting\n");
                         av_frame_free(&swr_frame);
                         return -1;
                     }
    
                     // 将重采样后的帧发送给编码器
                     if (avcodec_send_frame(encodec_ctx, swr_frame) == 0)
                     {
                         while (avcodec_receive_packet(encodec_ctx, out_packet) == 0)
                         {
                             // 正确设置数据包中的流索引
                             out_packet->stream_index = out_stream->index;
    
                             // 调整时间戳,使其基于输出流的时间基
                             av_packet_rescale_ts(out_packet, encodec_ctx->time_base, out_stream->time_base);
    
                             // 写入一个编码的数据包到输出文件
                             if (av_interleaved_write_frame(out_fmt_ctx, out_packet) < 0)
                             {
                                 fprintf(stderr, "Error while writing output packet\n");
                                 break;
                             }
                         }
                     }
                     av_frame_unref(swr_frame); // 清理帧数据以便重用
    

完整代码

extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libswresample/swresample.h>
}
#include <string>
#include <iostream>
using namespace std;

int initialize_encoder(const char *output_filename, AVFormatContext **out_fmt_ctx, AVCodecContext **enc_ctx, AVStream **out_stream,AVPacket **outpack)
{
    AVOutputFormat *out_fmt = NULL;
    AVCodec *encoder = NULL;
    AVCodecContext *encoder_ctx = NULL;
    AVStream *stream = NULL;

    // 设置输出文件的格式与路径
    out_fmt = av_guess_format(NULL, output_filename, NULL);
    if (!out_fmt)
    {
        fprintf(stderr, "could not guess file format\n");
        return -1;
    }

    // 打开输出格式的上下文
    if (avformat_alloc_output_context2(out_fmt_ctx, out_fmt, NULL, output_filename) < 0)
    {
        fprintf(stderr, "could not create output context\n");
        return -1;
    }

    // 创建输出流
    stream = avformat_new_stream(*out_fmt_ctx, NULL);
    if (!stream)
    {
        fprintf(stderr, "could not create output stream\n");
        return -1;
    }

    // 查找编码器
    encoder = avcodec_find_encoder(AV_CODEC_ID_MP3);
    if (!encoder)
    {
        fprintf(stderr, "Codec not found\n");
        return -1;
    }

    // 创建编码器上下文
    encoder_ctx = avcodec_alloc_context3(encoder);
    if (!encoder_ctx)
    {
        fprintf(stderr, "Could not allocate audio codec context\n");
        return -1;
    }

    // 设置编码器参数
    encoder_ctx->bit_rate = 192000;                    // 比特率为192kbps
    encoder_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;      // 采样格式
    encoder_ctx->sample_rate = 48000;                  // 采样率
    encoder_ctx->channel_layout = AV_CH_LAYOUT_STEREO; // 双声道布局
    encoder_ctx->channels = 2;

    // 打开编码器
    if (avcodec_open2(encoder_ctx, encoder, NULL) < 0)
    {
        fprintf(stderr, "Could not open codec\n");
        avcodec_free_context(&encoder_ctx);
        return -1;
    }

    // 给输出流设置编码器
    if (avcodec_parameters_from_context(stream->codecpar, encoder_ctx) < 0)
    {
        fprintf(stderr, "Failed to copy encoder parameters to output stream\n");
        avcodec_free_context(&encoder_ctx);
        return -1;
    }

    // 打开输出文件
    if (!((*out_fmt_ctx)->oformat->flags & AVFMT_NOFILE))
    {
        if (avio_open(&(*out_fmt_ctx)->pb, output_filename, AVIO_FLAG_WRITE) < 0)
        {
            fprintf(stderr, "Could not open output file\n");
            return -1;
        }
    }

    // 写入文件头部
    if (avformat_write_header((*out_fmt_ctx), NULL) < 0)
    {
        fprintf(stderr, "Error occurred when opening output file\n");
        return -1;
    }

    *enc_ctx = encoder_ctx;
    *out_stream = stream;
    //初始化输出的数据包
    *outpack = av_packet_alloc();
    return 0; // 成功
}


int initialize_decoder(const char *input_filename, AVFormatContext **fmt_ctx, AVCodecContext **codec_ctx, int *audio_stream_index)
{
    AVCodec *codec = NULL;

    if (avformat_open_input(fmt_ctx, input_filename, NULL, NULL) < 0)
    {
        fprintf(stderr, "Could not open input file\n");
        return -1;
    }

    if (avformat_find_stream_info(*fmt_ctx, NULL) < 0)
    {
        fprintf(stderr, "Could not find stream information\n");
        return -1;
    }

    *audio_stream_index = av_find_best_stream(*fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0);
    if (*audio_stream_index < 0)
    {
        fprintf(stderr, "Could not find audio stream in input file\n");
        return -1;
    }

    *codec_ctx = avcodec_alloc_context3(codec);
    if (!(*codec_ctx))
    {
        fprintf(stderr, "Could not allocate audio codec context\n");
        return -1;
    }

    if (avcodec_parameters_to_context(*codec_ctx, (*fmt_ctx)->streams[*audio_stream_index]->codecpar) < 0)
    {
        fprintf(stderr, "Could not copy codec parameters to codec context\n");
        return -1;
    }

    if (avcodec_open2(*codec_ctx, codec, NULL) < 0)
    {
        fprintf(stderr, "Could not open codec\n");
        return -1;
    }


    return 0; // 成功
}

int initialize_resampler(SwrContext **swr_ctx, AVCodecContext *codec_ctx, AVFrame **swr_frame)
{
    *swr_ctx = swr_alloc_set_opts(NULL,                      // ctx
                                  AV_CH_LAYOUT_STEREO,       // 输出的channel 的布局
                                  AV_SAMPLE_FMT_FLTP,        // 输出的采样格式
                                  48000,                     // 输出的采样率
                                  codec_ctx->channel_layout, // 输入的channel布局
                                  codec_ctx->sample_fmt,     // 输入的采样格式
                                  codec_ctx->sample_rate,    // 输入的采样率
                                  0, NULL);
    if (!(*swr_ctx))
    {
        fprintf(stderr, "Could not allocate resampler context\n");
        return -1;
    }

    if (swr_init(*swr_ctx) < 0)
    {
        fprintf(stderr, "Could not initialize the resampling context\n");
        swr_free(swr_ctx);
        return -1;
    }

    *swr_frame = av_frame_alloc();
    if (!(*swr_frame))
    {
        fprintf(stderr, "Could not allocate resampled frame\n");
        swr_free(swr_ctx);
        return -1;
    }

    return 0; // 成功
}


int main()
{


    AVFormatContext *fmt_ctx = NULL;
    AVOutputFormat *out_fmt = NULL;      // 输出格式
    AVFormatContext *out_fmt_ctx = NULL; // 输出格式上下文
    AVCodecContext *codec_ctx = NULL;    // 解码器上下文
    AVCodec *codec = NULL;               // 解码器
    AVCodec *encodec = NULL;             // 编码器
    AVCodecContext *encodec_ctx = NULL;  // 编码器上下文
    AVPacket *packet;
    AVFrame *frame;
    AVStream *out_stream = NULL; // 输出流
    AVPacket *out_packet;
    int audio_stream_index;
    SwrContext *swr_ctx;
    AVFrame *swr_frame;
    int ret;

    ret = initialize_encoder("output.mp3", &out_fmt_ctx, &encodec_ctx, &out_stream,&out_packet);
    if (ret != 0)
    {
        fprintf(stderr, "init encode failed\n");
        return -1;
    }
 

     // 初始化解码器
    ret = initialize_decoder("test.mp3", &fmt_ctx, &codec_ctx, &audio_stream_index);
    if (ret != 0)
    {
        fprintf(stderr, "init decode failed\n");
        return -1;
    }

    packet = av_packet_alloc(); // 初始化数据包
    frame = av_frame_alloc();
   
     // 初始化重采样上下文
    ret = initialize_resampler(&swr_ctx, codec_ctx, &swr_frame);
    if (ret != 0)
    {
        fprintf(stderr, "init resampler failed\n");
        return -1;
    }
   
    // 解码 ---- 重采样  -----编码
    while (av_read_frame(fmt_ctx, packet) >= 0)
    {
        if (packet->stream_index == audio_stream_index)
        {
            if (avcodec_send_packet(codec_ctx, packet) == 0)
            {
                while (avcodec_receive_frame(codec_ctx, frame) == 0)
                {
                    // 确保有足够的缓冲区来容纳转换后的数据
                    swr_frame->channel_layout = AV_CH_LAYOUT_STEREO;
                    swr_frame->format = AV_SAMPLE_FMT_FLTP;
                    swr_frame->sample_rate = 48000;
                    swr_frame->nb_samples = encodec_ctx->frame_size;  //与编码器帧大小保持一致

                    // 分配缓冲区
                    if (av_frame_get_buffer(swr_frame, 0) < 0)
                    {
                        fprintf(stderr, "Could not allocate output frame samples\n");
                        av_frame_free(&swr_frame);
                        return -1;
                    }

                    // 执行重采样
                    if (swr_convert_frame(swr_ctx, swr_frame, frame) < 0)
                    {
                        fprintf(stderr, "Error while converting\n");
                        av_frame_free(&swr_frame);
                        return -1;
                    }

                    // 将重采样后的帧发送给编码器
                    if (avcodec_send_frame(encodec_ctx, swr_frame) == 0)
                    {
                        while (avcodec_receive_packet(encodec_ctx, out_packet) == 0)
                        {
                            // 正确设置数据包中的流索引
                            out_packet->stream_index = out_stream->index;

                            // 调整时间戳,使其基于输出流的时间基
                            av_packet_rescale_ts(out_packet, encodec_ctx->time_base, out_stream->time_base);

                            // 写入一个编码的数据包到输出文件
                            if (av_interleaved_write_frame(out_fmt_ctx, out_packet) < 0)
                            {
                                fprintf(stderr, "Error while writing output packet\n");
                                break;
                            }
                        }
                    }
                    av_frame_unref(swr_frame); // 清理帧数据以便重用
                }
            }
        }
        av_packet_unref(packet);
    }


// 收尾工作
    // 写入文件尾部信息
    if (av_write_trailer(out_fmt_ctx) < 0)
    {
        fprintf(stderr, "Error writing trailer of the output file\n");
    }

    // 关闭输出文件和释放输出上下文
    if (!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE))
    {
        avio_closep(&out_fmt_ctx->pb);
    }
    avformat_free_context(out_fmt_ctx);
    // 其他资源清理
    av_packet_free(&out_packet);
    av_frame_free(&frame);
    av_frame_free(&swr_frame);
    av_packet_free(&packet);
    avcodec_close(codec_ctx);
    avcodec_close(encodec_ctx);
    avformat_close_input(&fmt_ctx);
    swr_free(&swr_ctx);

    return 0;
}

效果展示

ffmpeg 可以查看MP3文件的具体参数

命令:ffmpeg -i test.mp3

Stream #0:0

  • Stream:表示这是一个流。
  • #0:0:表示这是第一个输入文件中的第一个流(通常第一个流是视频流,第二个流是音频流,依此类推)。

Audio: mp3

  • Audio:表示这个流是一个音频流。
  • mp3:表示音频编码格式是 MP3(MPEG Audio Layer III)。

44100 Hz

  • 表示音频采样率为 44.1 kHz(44100 赫兹),即每秒钟采集 44100 次音频样本。

stereo

  • 表示音频是立体声(双声道),即音频信号包含两个独立的声道。

fltp

  • 表示音频采样格式是 float planar(浮点平面格式),即每个音频样本用浮点数表示,并且每个声道的数据存储在单独的平面中。

320 kb/s

  • 表示音频比特率为 320 kbps(千比特每秒),即每秒钟有 320000 比特的数据传输速率。

经过重采样之后,再次查看参数

音频采样率改为了 48 kHz(48000 赫兹),并且正常播放

相关推荐
Say-hai5 小时前
音视频入门知识(七):时间戳及其音视频播放原理
音视频
Java搬砖组长5 小时前
youtube下载的视频怎么保存到本地
音视频
科技小E5 小时前
国标GB28181设备管理软件EasyGBS:P2P远程访问故障排查指南(设备端)
网络协议·智能路由器·音视频·p2p
神仙别闹6 小时前
基于C#实现的(WinForm)模拟操作系统文件管理系统
java·git·ffmpeg
Say-hai7 小时前
音视频入门知识(二)、图像篇
音视频
Fre丸子_19 小时前
ffmpeg之播放一个yuv视频
ffmpeg·音视频
9527华安20 小时前
FPGA多路MIPI转FPD-Link视频缩放拼接显示,基于IMX327+FPD953架构,提供2套工程源码和技术支持
fpga开发·架构·音视频
catmes21 小时前
设置浏览器声音或视频的自动播放策略
chrome·音视频·edge浏览器
yinqinggong21 小时前
从源码编译支持FFmpeg的OpenCV
opencv·ffmpeg
冰山一脚20131 天前
ffmpeg添加sps,pps
ffmpeg