32.FFmpeg+OpenGLES+OpenSLES播放器实现(六.FFmpeg音视频解码)

简介: 项目源码FFmpeg开发文档 解码分为软解码和硬解码,那么什么是软解码和硬解码,二者有什么区别?简单来说,在于是否使用CPU进行解码,最初视频解码都是通过CPU进行的,那时候视频分辨率较低,CPU完全可以胜任解码的工作,但是随着高清视频的出现,使用CPU进行解码的压力越来越大 软解码 使用CPU进行解码,所以就很容易造成CPU负载过大。

项目源码
FFmpeg开发文档
解码分为软解码和硬解码,那么什么是软解码和硬解码,二者有什么区别?简单来说,在于是否使用CPU进行解码,最初视频解码都是通过CPU进行的,那时候视频分辨率较低,CPU完全可以胜任解码的工作,但是随着高清视频的出现,使用CPU进行解码的压力越来越大

软解码

使用CPU进行解码,所以就很容易造成CPU负载过大。纯粹依靠CPU来解码,是在显卡本身不支持或者部分不支持硬件解码的前提下,将解压高清编码的任务交给CPU,这是基于硬件配置本身达不到硬解压要求的前提下的无奈之举。我们在解码过程中,应该在硬件支持的前提下优先使用硬解码。
对于一个超级电视而言,观看高清电影无疑是用户最大的诉求,而硬解码的优势就在于可以流畅的支持1080p甚至4K清晰度的电影播放,而不需要占用CPU,CPU就可以如释重负,轻松上阵,承担更多的其他任务。如果通过软解码的方式播放高清电影,CPU的负担较重,往往会出现卡顿、不流畅的现象。

硬解码

硬件解码就是通过显卡的视频加速功能对高清视频进行解码,使用非CPU进行,如GPU/VPU(GPU:图形处理器:Graphics Processing Unit,指计算机的显卡,VPU:Visual Processing Unit,视觉处理单元,由ATI提出的、用于区别于传统GPU的概念,实际二者均为显示处理核心,本质上并无任何区别)、专用的DSP、FPGA、ASIC芯片等,所以几乎不会占用CPU,部分产品在GPU硬件平台移植了优秀的软编码算法(如X264),解码质量基本等同于软编码。硬解码可以将CPU从繁重的视频解码中解放出来,使播放设备具备流畅播放高清视频的能力。显卡的GPU/VPU要比CPU更适合这类大数据量的、低难度的重复工作。

那么ffmpeg是如何进行软解码和硬解码的,分别来看

解码步骤

使用ffmpeg解码分为如下几步:
1.找到解码器
avcodec_find_decoder(软) avcodec_find_decoder_by_name(硬)
2.获取解码器上下文
avcodec_alloc_context3
3.填充解码器参数到上下文中
avcodec_parameters_to_context
4.打开解码器
avcodec_open2
5.将解封装得到的AVPacket发送到解码器中进行解码
avcodec_send_packet
6.从解码器中接收已经解码完成的数据存入AVFrame
avcodec_receive_frame
7.释放frame空间
av_frame_unref

具体到代码中看一看音视频的软解码和硬解码

视频软解码环境
    /***************************************video解码器*********************************************/
    //找到视频解码器(软解码)
    AVCodec *videoAVCodec = avcodec_find_decoder(avFormatContext->streams[videoIndex]->codecpar->codec_id);
    avcodec_open2 video failed!
    if (videoAVCodec == NULL){
        LOGE("avcodec_find_decoder failed !");
        return;
    }
    //初始化视频解码器上下文对象
    AVCodecContext *videoCodecContext = avcodec_alloc_context3(videoAVCodec);
    //根据所提供的编解码器的值填充编解码器上下文参数
    avcodec_parameters_to_context(videoCodecContext,avFormatContext->streams[videoIndex]->codecpar);
    //设置视频解码器解码的线程数,解码时将会以你设定的线程进行解码
    videoCodecContext->thread_count = 8;
    //打开解码器
    result = avcodec_open2(videoCodecContext,NULL,NULL);
    if (result != 0){
        LOGE("avcodec_open2 video failed! %s",av_err2str(result));
        return;
    }
    /***********************************************************************************************/
视频硬解码环境
    //硬解码,硬解码需要Jni_OnLoad中做设置否则ffmpeg_player_error: avcodec_open2 video failed!
    AVCodec *videoAVCodec = avcodec_find_decoder_by_name("h264_mediacodec");
    if (videoAVCodec == NULL){
        LOGE("avcodec_find_decoder failed !");
        return;
    }
    //初始化视频解码器上下文对象
    AVCodecContext *videoCodecContext = avcodec_alloc_context3(videoAVCodec);
    //根据所提供的编解码器的值填充编解码器上下文参数
    avcodec_parameters_to_context(videoCodecContext,avFormatContext->streams[videoIndex]->codecpar);
    //设置视频解码器解码的线程数,解码时将会以你设定的线程进行解码
    videoCodecContext->thread_count = 8;
    //打开解码器
    result = avcodec_open2(videoCodecContext,NULL,NULL);
    if (result != 0){
        LOGE("avcodec_open2 video failed! %s",av_err2str(result));
        return;
    }

可以看到软硬解码的区别也只是在于获取解码器的那一步操作

音频软解码环境
    /***************************************audio解码器*********************************************/

    //找到音频解码器(软解码)
    AVCodec *audioAVCodec = avcodec_find_decoder(avFormatContext->streams[audioIndex]->codecpar->codec_id);
    //初始化音频解码器上下文对象
    AVCodecContext *audioCodecContext = avcodec_alloc_context3(audioAVCodec);
    //根据所提供的编解码器的值填充编解码器上下文参数
    avcodec_parameters_to_context(audioCodecContext,avFormatContext->streams[audioIndex]->codecpar);
    //设置音频解码器解码的线程数,解码时将会以你设定的线程进行解码
    audioCodecContext->thread_count = 1;
    //打开音频解码器
    result = avcodec_open2(audioCodecContext,NULL,NULL);
    if (result != 0){
        LOGE("avcodec_open2 audio failed!");
        return;
    }
    /***********************************************************************************************/
音频硬解码环境
/***************************************audio解码器*********************************************/

    //找到音频解码器(软解码)
    //硬解码
    AVCodec *audioAVCodec = avcodec_find_decoder_by_name("h264_mediacodec");
    //初始化音频解码器上下文对象
    AVCodecContext *audioCodecContext = avcodec_alloc_context3(audioAVCodec);
    //根据所提供的编解码器的值填充编解码器上下文参数
    avcodec_parameters_to_context(audioCodecContext,avFormatContext->streams[audioIndex]->codecpar);
    //设置音频解码器解码的线程数,解码时将会以你设定的线程进行解码
    audioCodecContext->thread_count = 1;
    //打开音频解码器
    result = avcodec_open2(audioCodecContext,NULL,NULL);
    if (result != 0){
        LOGE("avcodec_open2 audio failed!");
        return;
    }
    /***********************************************************************************************/

同样也是区别在于解码器的获取

获取到解码器之后解码的过程代码如下:

    for (;;) {

        //********************测试每秒解码帧数代码*******************
        if(GetNowMs() - start >=3000){
            LOGI("now decode fps is %d",frameCount/3);
            start = GetNowMs();
            frameCount = 0;
        }
        //********************测试每秒解码帧数代码*******************

        //Return the next frame of a stream.
        int read_result = av_read_frame(avFormatContext,avPacket);
        if(read_result != 0){
            //读取到结尾处,从20秒位置继续开始播放
            LOGI("读取到结尾处 %s",av_err2str(read_result));
            //跳转到指定的position播放,最后一个参数表示
            //int pos = 200000 * r2d(avFormatContext->streams[videoIndex]->time_base);
            //av_seek_frame(avFormatContext,videoIndex,pos,AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME );
            //LOGI("avFormatContext->streams[videoIndex]->time_base= %d",avFormatContext->streams[videoIndex]->time_base);
            //continue;
            break;
        }
        LOGW("stream = %d size =%d pts=%lld flag=%d pos = %d",
             avPacket->stream_index,avPacket->size,avPacket->pts,avPacket->flags,avPacket->pos
        );

        //解码测试
        if(avPacket->stream_index != videoIndex){
            continue;
        }

        AVCodecContext *codecContext = videoCodecContext;
        if (avPacket->stream_index == audioIndex){
            codecContext = audioCodecContext;
        }

        //将packet发送到解码器中进行解码
        read_result = avcodec_send_packet(videoCodecContext,avPacket);
        if (read_result != 0){
            LOGE("avcodec_send_packet failed!");
            continue;
        }

        for(;;){
            //从解码器中返回的已经解码的数据
            read_result = avcodec_receive_frame(codecContext,avFrame);
            if(read_result != 0){
                LOGE("avcodec_receive_frame failed!");
                break;
            }

            //********************测试每秒解码帧数代码*******************
            //说明解码的是视频,
            if(codecContext == videoCodecContext) {
                frameCount++;
            }
            //********************测试每秒解码帧数代码*******************

            LOGW("avcodec_receive_frame %lld",avFrame->pts);
        }

        //packet使用完成之后执行,否则内存会急剧增长
        //不再引用这个packet指向的空间,并且将packet置为default状态
        av_packet_unref(avPacket);
    }
软硬解码已经多线程解码效率测试结果

neon 单线程下解码视频结果:
每秒帧数27~68
CPU占用16%左右
内存占用67M左右

neon 八线程下解码视频结果
每秒解码帧数100~170帧
CPU占用70%左右
内存占用90M左右

neon h264_mediacodec硬解码 设置单线程
每秒解码帧数46~58,硬解码是一个固定值,这是由于计算误差产生的
CPU占用3%左右
内存占用23M左右

neon h264_mediacodec硬解码 设置8线程
每秒解码帧数46~85,线程数对帧率无影响,因为硬解码帧率是固定的
CPU和内存的占用可忽略不计

总结一下

1.软解码相较于硬解码,对内存和CPU的开销都明显很大
2.硬解码的解码帧率是固定的,但是几乎不占用CPU

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
16天前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
78 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
3月前
|
Web App开发 5G Linux
FFmpeg开发笔记(四十四)毕业设计可做的几个拉满颜值的音视频APP
一年一度的毕业季来临,计算机专业的毕业设计尤为重要,不仅关乎学业评价还积累实战经验。选择紧跟5G技术趋势的音视频APP作为课题极具吸引力。这里推荐三类应用:一是融合WebRTC技术实现视频通话的即时通信APP;二是具备在线直播功能的短视频分享平台,涉及RTMP/SRT等直播技术;三是具有自定义动画特效及卡拉OK歌词字幕功能的视频剪辑工具。这些项目不仅技术含量高,也符合市场需求,是毕业设计的理想选择。
74 6
FFmpeg开发笔记(四十四)毕业设计可做的几个拉满颜值的音视频APP
|
2月前
|
Android开发 计算机视觉 C++
FFmpeg开发笔记(五十一)适合学习研究的几个音视频开源框架
音视频编程对许多程序员来说是一片充满挑战的领域,但借助如OpenCV、LearnOpenGL、FFmpeg、OBS Studio及VLC media player等强大的开源工具,可以降低入门门槛。这些框架不仅覆盖了计算机视觉、图形渲染,还包括多媒体处理与直播技术,通过多种编程语言如Python、C++的应用,使得音视频开发更为便捷。例如,OpenCV支持跨平台的视觉应用开发,FFmpeg则擅长多媒体文件的处理与转换,而VLC media player则是验证音视频文件质量的有效工具。
83 0
FFmpeg开发笔记(五十一)适合学习研究的几个音视频开源框架
|
4月前
|
数据采集 大数据 Python
FFmpeg 在爬虫中的应用案例:流数据解码详解
在大数据背景下,网络爬虫与FFmpeg结合,高效采集小红书短视频。需准备FFmpeg、Python及库如Requests和BeautifulSoup。通过设置User-Agent、Cookie及代理IP增强隐蔽性,解析HTML提取视频链接,利用FFmpeg下载并解码视频流。示例代码展示完整流程,强调代理IP对避免封禁的关键作用,助你掌握视频数据采集技巧。
FFmpeg 在爬虫中的应用案例:流数据解码详解
|
2月前
用ffmpeg提取合并音视频
用ffmpeg提取合并音视频
|
4月前
|
C#
C#进程调用FFmpeg操作音视频
因为公司需要对音视频做一些操作,比如说对系统用户的发音和背景视频进行合成,以及对多个音视频之间进行合成,还有就是在指定的源背景音频中按照对应的规则在视频的多少秒钟内插入一段客户发音等一些复杂的音视频操作。本篇文章主要讲解的是使用C#进程(Process)调用FFmpeg.exe进行视频合并、音频合并、音频与视频合并成视频这几个简单的音视频操作。
|
24天前
|
编解码 语音技术 内存技术
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
《FFmpeg开发实战:从零基础到短视频上线》一书中的“5.1.2 把音频流保存为PCM文件”章节介绍了将媒体文件中的音频流转换为原始PCM音频的方法。示例代码直接保存解码后的PCM数据,保留了原始音频的采样频率、声道数量和采样位数。但在实际应用中,有时需要特定规格的PCM音频。例如,某些语音识别引擎仅接受16位PCM数据,而标准MP3音频通常采用32位采样,因此需将32位MP3音频转换为16位PCM音频。
42 0
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
|
29天前
|
XML 开发工具 Android开发
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
ExoPlayer最初是为了解决Android早期MediaPlayer控件对网络视频兼容性差的问题而推出的。现在,Android官方已将其升级并纳入Jetpack的Media3库,使其成为音视频操作的统一引擎。新版ExoPlayer支持多种协议,解决了设备和系统碎片化问题,可在整个Android生态中一致运行。通过修改`build.gradle`文件、布局文件及Activity代码,并添加必要的权限,即可集成并使用ExoPlayer进行网络视频播放。具体步骤包括引入依赖库、配置播放界面、编写播放逻辑以及添加互联网访问权限。
115 1
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
|
1月前
|
Web App开发 安全 程序员
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
多年的互联网寒冬在今年尤为凛冽,坚守安卓开发愈发不易。面对是否转行或学习新技术的迷茫,安卓程序员可从三个方向进阶:1)钻研谷歌新技术,如Kotlin、Flutter、Jetpack等;2)拓展新功能应用,掌握Socket、OpenGL、WebRTC等专业领域技能;3)结合其他行业,如汽车、游戏、安全等,拓宽职业道路。这三个方向各有学习难度和保饭碗指数,助你在安卓开发领域持续成长。
58 1
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
|
2月前
|
XML Java Android开发
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer
GSYVideoPlayer是一款国产移动端视频播放器,支持弹幕、滤镜、广告等功能,采用IJKPlayer、Media3(EXOPlayer)、MediaPlayer及AliPlayer多种内核。截至2024年8月,其GitHub星标数达2万。集成时需使用新版Android Studio,并按特定步骤配置依赖与权限。提供了NormalGSYVideoPlayer、GSYADVideoPlayer及ListGSYVideoPlayer三种控件,支持HLS、RTMP等多种直播链接。
87 18
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer