探索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在视频和音频播放速度控制中的应用,并研究了各种成功的实践。在理论和实践的结合中,我们不仅了解了技术的具体实现,还掌握了如何自我实现。

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

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

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

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

目录
相关文章
|
7月前
|
存储 缓存 编解码
【FFmpeg 视频播放】深入理解多媒体播放:同步策略、缓冲技术与性能优化(一)
【FFmpeg 视频播放】深入理解多媒体播放:同步策略、缓冲技术与性能优化
357 0
|
2月前
|
XML 开发工具 Android开发
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
ExoPlayer最初是为了解决Android早期MediaPlayer控件对网络视频兼容性差的问题而推出的。现在,Android官方已将其升级并纳入Jetpack的Media3库,使其成为音视频操作的统一引擎。新版ExoPlayer支持多种协议,解决了设备和系统碎片化问题,可在整个Android生态中一致运行。通过修改`build.gradle`文件、布局文件及Activity代码,并添加必要的权限,即可集成并使用ExoPlayer进行网络视频播放。具体步骤包括引入依赖库、配置播放界面、编写播放逻辑以及添加互联网访问权限。
170 1
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
|
7月前
FFmpeg开发笔记(十八)FFmpeg兼容各种音频格式的播放
《FFmpeg开发实战》一书中,第10章示例程序playaudio.c原本仅支持mp3和aac音频播放。为支持ogg、amr、wma等非固定帧率音频,需进行三处修改:1)当frame_size为0时,将输出采样数量设为512;2)遍历音频帧时,计算实际采样位数以确定播放数据大小;3)在SDL音频回调函数中,确保每次发送len字节数据。改进后的代码在chapter10/playaudio2.c,可编译运行播放ring.ogg测试,成功则显示日志并播放铃声。
126 1
FFmpeg开发笔记(十八)FFmpeg兼容各种音频格式的播放
|
7月前
|
编解码 计算机视觉 索引
使用ffmpeg MP4转 m3u8并播放 实测!!
使用ffmpeg MP4转 m3u8并播放 实测!!
343 1
|
7月前
|
网络协议 API 网络安全
探讨TCP传输视频流并利用FFmpeg进行播放的过程
探讨TCP传输视频流并利用FFmpeg进行播放的过程
692 0
|
7月前
|
存储 算法 C++
【FFmpeg 视频播放】深入理解多媒体播放:同步策略、缓冲技术与性能优化(二)
【FFmpeg 视频播放】深入理解多媒体播放:同步策略、缓冲技术与性能优化
320 0
|
7月前
|
存储 算法 前端开发
深入理解FFmpeg音视频编程:处理封装、解码、播放 队列与回放策略
深入理解FFmpeg音视频编程:处理封装、解码、播放 队列与回放策略
316 0
|
2月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
180 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
2月前
|
编解码 语音技术 内存技术
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
《FFmpeg开发实战:从零基础到短视频上线》一书中的“5.1.2 把音频流保存为PCM文件”章节介绍了将媒体文件中的音频流转换为原始PCM音频的方法。示例代码直接保存解码后的PCM数据,保留了原始音频的采样频率、声道数量和采样位数。但在实际应用中,有时需要特定规格的PCM音频。例如,某些语音识别引擎仅接受16位PCM数据,而标准MP3音频通常采用32位采样,因此需将32位MP3音频转换为16位PCM音频。
77 0
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
|
2月前
|
Web App开发 安全 程序员
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
多年的互联网寒冬在今年尤为凛冽,坚守安卓开发愈发不易。面对是否转行或学习新技术的迷茫,安卓程序员可从三个方向进阶:1)钻研谷歌新技术,如Kotlin、Flutter、Jetpack等;2)拓展新功能应用,掌握Socket、OpenGL、WebRTC等专业领域技能;3)结合其他行业,如汽车、游戏、安全等,拓宽职业道路。这三个方向各有学习难度和保饭碗指数,助你在安卓开发领域持续成长。
82 1
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势

热门文章

最新文章