探索FFmpeg:实现自定义播放速度的全方位指南(三)

简介: 探索FFmpeg:实现自定义播放速度的全方位指南

探索FFmpeg:实现自定义播放速度的全方位指南(二)https://developer.aliyun.com/article/1464307


5.3 综合应用:完成一个基于FFmpeg的简易播放器(Integrated Application: Completing a Simple FFmpeg-based Player)

FFmpeg是一个强大的工具,它提供了许多函数和库来帮助我们处理音频和视频。在FFmpeg中,我们可以使用libavfilter库中的atempo滤镜来改变音频的播放速度。这个滤镜使用了一个复杂的算法来保证音质的同时改变音频的播放速度。我们只需要简单地调用这个滤镜,就可以实现我们的需求。

总的来说,改变音频的播放速度是一个复杂的过程,但是有许多现有的工具和库可以帮助我们实现。只需要理解基本的原理,并且熟悉这些工具和库的使用,就可以实现我们的需求。

以下是一个使用FFmpeg和SDL库的简化示例,用于演示如何实现基本的播放和播放速度控制:

#include <iostream>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <libswscale/swscale.h>
#include <SDL2/SDL.h>
}
int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: player <file>";
        return 1;
    }
    const char* filename = argv[1];
    // Initialize FFmpeg
    av_register_all();
    // Open file with FFmpeg and retrieve video stream
    AVFormatContext* formatCtx = nullptr;
    if (avformat_open_input(&formatCtx, filename, nullptr, nullptr) != 0) {
        std::cerr << "ERROR: Couldn't open input file.";
        return 2;
    }
    if (avformat_find_stream_info(formatCtx, nullptr) < 0) {
        std::cerr << "ERROR: Couldn't find stream information.";
        return 3;
    }
    int videoStream = -1;
    for (unsigned int i = 0; i < formatCtx->nb_streams; i++) {
        if (formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStream = i;
            break;
        }
    }
    if (videoStream == -1) {
        std::cerr << "ERROR: Couldn't find a video stream.";
        return 4;
    }
    // Prepare video display using SDL
    AVCodecContext* codecCtx = formatCtx->streams[videoStream]->codec;
    int width = codecCtx->width;
    int height = codecCtx->height;
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
    SDL_Window* window = SDL_CreateWindow("Simple Video Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, 0);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
    SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, width, height);
    // Create SwsContext for video scaling and format conversion
    SwsContext* swsCtx = sws_getContext(width, height, codecCtx->pix_fmt, width, height, AV_PIX_FMT_YUV420P, SWS_BILINEAR, nullptr, nullptr, nullptr);
    // Reads packets from the file
    AVPacket packet;
    AVFrame* frame = av_frame_alloc();
    AVFrame* frameYUV = av_frame_alloc();
    int numBytes = avpicture_get_size(AV_PIX_FMT_YUV420P, width, height);
    uint8_t* buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t));
    avpicture_fill((AVPicture*)frameYUV, buffer, AV_PIX_FMT_YUV420P, width, height);
    int frameFinished;
    while (av_read_frame(formatCtx, &packet) >= 0) {
        if (packet.stream_index == videoStream) {
            avcodec_decode_video2(codecCtx, frame, &frameFinished, &packet);
            if (frameFinished) {
                // Scale and convert the frame to YUV420P format
                sws_scale(swsCtx, (uint8_t const* const*)frame->data, frame->linesize, 0, height, frameYUV->data, frameYUV->linesize);
                // Wait for the desired playback speed
                int64_t pts = av_frame_get_best_effort_timestamp(frame);
                SDL_Delay(av_rescale_q(pts, formatCtx->streams[videoStream]->time_base, AV_TIME_BASE_Q) / 1000);
                // Render the frame on the screen
                SDL_UpdateYUVTexture(texture, nullptr, frameYUV->data[0], frameYUV->linesize[0], frameYUV->data[1], frameYUV->linesize[1], frameYUV->data[2], frameYUV->linesize[2]);
                SDL_RenderClear(renderer);
                SDL_RenderCopy(renderer, texture, nullptr, nullptr);
                SDL_RenderPresent(renderer);
            }
        }
        av_free_packet(&packet);
    }
    // Cleanup
    av_free(buffer);
    av_frame_free(&frameYUV);
    av_frame_free(&frame);
    sws_freeContext(swsCtx);
    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    avcodec_close(codecCtx);
    avformat_close_input(&formatCtx);
    return 0;
}

以上示例使用C++编写,需要安装FFmpeg库和SDL2库。代码首先使用FFmpeg库打开和解码媒体文件,然后使用SDL库显示视频帧。请注意,FFmpeg和SDL都具有丰富的文档和教程,可以用来扩展此示例以满足您的具体要求。在此基础上,您可以通过调整计时部分来改变播放速度。

可以使用libavfilter库中的atempo滤镜来改变音频的播放速度。atempo滤镜可用于在不改变音高的情况下调整音频速度,播放速度可设置在0.5到2.0之间。以下是一个在上述示例的基础上,加入libavfilter库以处理音频播放速度的修改示例:

请注意,在此示例中添加了音频解码和音频处理功能,所以我简化了一部分代码来专注于这些修改。

首先,在文件开头包含libavfilter库:

extern "C" {
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
}

接下来,在main()函数中适当的位置初始化libavfilter库:

avfilter_register_all();

为音频过滤器添加一些变量,并在找到音频流之后初始化这些变量:

AVFilter* abuffersrc = nullptr;
AVFilter* abuffersink = nullptr;
AVFilterInOut* outputs = nullptr;
AVFilterInOut* inputs = nullptr;
AVFilterGraph* filterGraph = nullptr;
// Find audio stream and set up filter
int audioStream = -1;
for (unsigned int i = 0; i < formatCtx->nb_streams; i++) {
    if (formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
        audioStream = i;
        break;
    }
}
if (audioStream != -1) {
    abuffersrc = avfilter_get_by_name("abuffer");
    abuffersink = avfilter_get_by_name("abuffersink");
    outputs = avfilter_inout_alloc();
    inputs = avfilter_inout_alloc();
    filterGraph = avfilter_graph_alloc();
    AVCodecContext* audioCodecCtx = formatCtx->streams[audioStream]->codec;
    char chLayout[64];
    int64_t inputChLayout = audioCodecCtx->channel_layout;
    snprintf(chLayout, sizeof(chLayout), "channel_layout=0x%" PRIx64, inputChLayout);
    char sampleRate[64];
    int inputSampleRate = audioCodecCtx->sample_rate;
    snprintf(sampleRate, sizeof(sampleRate), "sample_rate=%d", inputSampleRate);
    char bufferSrcArgs[512];
    snprintf(bufferSrcArgs, sizeof(bufferSrcArgs) - 1, "time_base=1/%d:sample_fmt=%s:%s:sample_rate=%d", inputSampleRate, av_get_sample_fmt_name(audioCodecCtx->sample_fmt), chLayout, input SampleRate);
    memset(bufferSrcArgs, 0, sizeof(bufferSrcArgs));
    snprintf(bufferSrcArgs, sizeof(bufferSrcArgs), "sample_rate=%d:sample_fmt=%d:channels=%d:time_base=1/%d:channel_layout=0x%" PRIx64,
             inputSampleRate, audioCodecCtx->sample_fmt, audioCodecCtx->channels, inputSampleRate, inputChLayout);
    avfilter_graph_create_filter(&abuffersrc_ctx, abuffersrc, "in", bufferSrcArgs, nullptr, filterGraph);
    avfilter_graph_create_filter(&abuffersink_ctx, abuffersink, "out", nullptr, nullptr, filterGraph);
    outputs->name = av_strdup("in");
    outputs->filter_ctx = abuffersrc_ctx;
    outputs->pad_idx = 0;
    outputs->next = nullptr;
    inputs->name = av_strdup("out");
    inputs->filter_ctx = abuffersink_ctx;
    inputs->pad_idx = 0;
    inputs->next = nullptr;
    const char *filterDesc = "atempo=1.25";  // Adjust audio speed here, 1.25 chosen as an example
    int result = avfilter_graph_parse_ptr(filterGraph, filterDesc, &inputs, &outputs, nullptr);
    if (result < 0) {
        std::cerr << "ERROR: Couldn't initialize audio filter graph.";
        return 5;
    }
    avfilter_graph_config(filterGraph, nullptr);
    AVFilterContext* abuffersrc_ctx = nullptr;
    AVFilterContext* abuffersink_ctx = nullptr;
}

在音频处理的循环中,使用这个音频滤镜:

if (packet.stream_index == audioStream) {
    avcodec_decode_audio4(audioCodecCtx, frame, &frameFinished, &packet);
    if (frameFinished) {
        AVFilterLink* inlink = filterGraph->filters->inputs;
        frame->pts = av_frame_get_best_effort_timestamp(frame);
        if (av_buffersrc_add_frame(abuffersrc_ctx, frame) >= 0) {
            while (av_buffersink_get_frame(abuffersink_ctx, frame) >= 0) {
                // Process the filtered audio frame, e.g., send it to SDL for playback
            }
        }
    }
}

在程序清理阶段,销毁滤镜相关的内存:

if (audioStream != -1) {
    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);
    avfilter_graph_free(&filterGraph);
}

这个示例展示了如何在视频播放器中使用libavfilter库的atempo滤镜来实现音频播放速度的改变。您可以根据需要调整这个示例以适应您的项目。

结语

在我们的探索旅程中,我们深入了解了播放速度控制的基础知识,学习了FFmpeg在视频和音频播放速度控制中的应用,并研究了各种成功的实践。在理论和实践的结合中,我们不仅了解了技术的具体实现,还掌握了如何自我实现。

这是一个不断学习和探索的过程,正如心理学告诉我们的那样,学习是一种持续的、积极向上的行为。它需要我们保持好奇心,不断寻找新的知识,克服遇到的困难,从失败中汲取教训,从成功中获取动力。

这个博客只是你学习之旅的一部分,希望它能为你提供有用的信息,引发你的思考,激发你的创新精神。如果你觉得它有帮助,欢迎收藏和点赞,你的支持是我们最大的鼓励。

请记住,学习是一个过程,而不是一个结果。我们应该享受学习过程,不断探索未知,追求新的知识。因此,持之以恒,但也要学习有度,给自己充足的休息,以保持最佳的学习状态。

无论你是在掌握新的技能,还是在深化你的知识,你的进步都是值得赞扬的。每一步都是向前的一步,每一次的努力都是成功的一部分。因此,让我们一起积极向上,继续探索和学习。

目录
相关文章
|
2月前
|
存储 缓存 编解码
【FFmpeg 视频播放】深入理解多媒体播放:同步策略、缓冲技术与性能优化(一)
【FFmpeg 视频播放】深入理解多媒体播放:同步策略、缓冲技术与性能优化
61 0
|
2月前
|
存储 编解码 调度
剖析ffmpeg视频解码播放:时间戳的处理
剖析ffmpeg视频解码播放:时间戳的处理
58 0
|
28天前
|
编解码 计算机视觉 索引
使用ffmpeg MP4转 m3u8并播放 实测!!
使用ffmpeg MP4转 m3u8并播放 实测!!
19 1
|
2月前
|
网络协议 API 网络安全
探讨TCP传输视频流并利用FFmpeg进行播放的过程
探讨TCP传输视频流并利用FFmpeg进行播放的过程
58 0
|
2月前
|
存储 算法 C++
【FFmpeg 视频播放】深入理解多媒体播放:同步策略、缓冲技术与性能优化(二)
【FFmpeg 视频播放】深入理解多媒体播放:同步策略、缓冲技术与性能优化
48 0
|
2月前
|
存储 算法 前端开发
深入理解FFmpeg音视频编程:处理封装、解码、播放 队列与回放策略
深入理解FFmpeg音视频编程:处理封装、解码、播放 队列与回放策略
29 0
|
1月前
|
开发工具
使用FFmpeg4.3.1的SDK官方开发包编译ffmpeg.c(三)
使用FFmpeg4.3.1的SDK官方开发包编译ffmpeg.c(三)
18 0
|
4月前
|
Linux 编译器 数据安全/隐私保护
Windows10 使用MSYS2和VS2019编译FFmpeg源代码-测试通过
FFmpeg作为一个流媒体的整体解决方案,在很多项目中都使用了它,如果我们也需要使用FFmpeg进行开发,很多时候我们需要将源码编译成动态库或者静态库,然后将库放入到我们的项目中,这样我们就能在我们的项目中使用FFmpeg提供的接口进行开发。关于FFmpeg的介绍这里就不过多说明。
84 0
|
8月前
|
C++ Windows
FFmpeg入门及编译 3
FFmpeg入门及编译
57 0
|
8月前
|
编解码 API 开发工具
FFmpeg入门及编译 1
FFmpeg入门及编译
101 0