基于FFmpeg的简单Android视频播放器

1. 模块分割

首先对这个视频播放器所采用的一些部件要清楚。这个播放器主要可以拆分为4个部分:

1.解码:FFmpeg

2.音频输出:OpenSLES

3.视频渲染:OpenGLES

这些框架都是基于C的api,因此这次我们的主要工作将会集中在NDK部分。而关于NDK的一些知识,之前的博客也有讲过,所以这个工程会是对之前知识的一次综合运用。

按照视频播放器的功能,我们将分出以下几个模块:

  1. 图像显示
  2. 音频输出
  3. 解码
  4. 播放控制
  5. 音视频同步

为了提高可移植性,对关键部件使用接口来规范其API接口。

1. IAudioPlayer:音频播放器接口。它规定的接口如下

class IAudioPlayer {
public:virtual bool create() = 0;virtual void release() = 0;virtual void start() = 0;virtual void stop() = 0;virtual bool isPlaying() = 0;virtual void setAudioFrameProvider(IAudioFrameProvider *provider) = 0;virtual void removeAudioFrameProvider(IAudioFrameProvider *provider) = 0;
};

2. IVideoPlayer:视频播放接口。

class IVideoPlayer {
public:virtual bool create() = 0;virtual void release() = 0;virtual void refresh() = 0;virtual void setVideoFrameProvider(IVideoFrameProvider *provider) = 0;virtual void removeVideoFrameProvider(IVideoFrameProvider *provider) = 0;virtual void setWindow(void *window) = 0;virtual void setSize(int32_t width, int32_t height) = 0;virtual bool isReady() = 0;
};

3. AudioFrame:存储解码好的音频数据。

对于播放器内部,播放的音频数据格式为16位PCM,44.1kHz采样率,双声道。为了避免每一段音频数据都要重新申请内存,我们将会复用AudioFrame,因此要给它设置一个最大音频数据存储空间。

struct AudioFrame{// present time stampint64_t pts;int16_t *data;int32_t sampleCount;int32_t maxDataSizeInByte = 0;AudioFrame(int32_t dataLenInByte){this->maxDataSizeInByte = dataLenInByte;pts = 0;sampleCount = 0;data = (int16_t *)malloc(maxDataSizeInByte);memset(data, 0, maxDataSizeInByte);}~AudioFrame(){if(data != NULL){free(data);}}
};

4. VideoFrame:存储解码好的视频数据:

对于播放器内部使用的视频数据格式,分辨率为1920*1080,像素格式RGB888,每种颜色一个字节,一个像素占3个字节。对于VideoFrame同样会复用。

struct VideoFrame
{int64_t pts;uint8_t *data;int32_t width;int32_t height;int32_t maxDataSizeInByte = 0;;VideoFrame(int32_t dataLenInByte){this->maxDataSizeInByte = dataLenInByte;data = (uint8_t *)malloc(maxDataSizeInByte);memset(data, 0, maxDataSizeInByte);}~VideoFrame(){if(data != NULL){free(data);}}
};

相关学习资料推荐,点击下方链接免费报名,先码住不迷路~】

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

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

5. IAudioFrameProvider:面向IAudioPlayer的音频数据的提供源,它为IAudioPlayer提供解码好的音频数据

由于要复用AudioFrame,因此要设置一个接口,让IAudioPlayer将使用完的AudioFrame归还给我们。

class IAudioFrameProvider {
public:virtual AudioFrame* getAudioFrame() = 0;virtual void putBackUsed(AudioFrame *data) = 0;
};

6. IVideoFrameProvider:和IAudioFrameProvider一样。

class IVideoFrameProvider {
public:virtual VideoFrame* getVideoFrame() = 0;virtual void putBackUsed(VideoFrame* data) = 0;
};

7. IMediaDataReceiver:用于接收解码好的音视频数据的接口。

它是用来维护并存储已经解码好的音视频数据和使用过的音视频数据。

class IMediaDataReceiver {
public:virtual void receiveAudioFrame(AudioFrame *audioData) = 0;virtual void receiveVideoFrame(VideoFrame *videoData) = 0;virtual AudioFrame* getUsedAudioFrame() = 0;virtual VideoFrame* getUsedVideoFrame() = 0;virtual void putUsedAudioFrame(AudioFrame *audioData) = 0;virtual void putUsedVideoFrame(VideoFrame *videoData) = 0;
};

8. BlockRecyclerQueue:同步复用队列。

c++内并没有线程安全的队列模型。因此我们自己实现一个。并且由于播放器内很多的数据都会需要复用,因此给这个队列加一个复用功能。这样,这个类内部会有两个队列,一个存储未使用的数据,一个存储已使用的数据。使用两把锁,分别对两个队列进行线程保护。当然,实际上你也可以以更小的粒度来考虑这件事,只要使用一个队列,然后对队列进行线程保护即可,至于里面存储的到底是用过的数据还是没用过的数据,完全可以由上层来决定。

播放器中的多线程都使用c++11自带的thread。

这个同步复用队列实际上就是生产者消费者模式中的管道。它有以下几个特点:

1.如果设置capacity=-1,那么这个队列是不限大小的。如果限制了大小,当内部存储的数据满的时候,put操作就会等待,这是为了防止解码器过快导致内存占用过高。

2.对于get操作和put操作,你可以通过设置wait来决定当数据空或满的时候是否等待。对于get操作,队列空时,如果wait = true,那么它就会一直等待直到有数据;如果wait = false,那么它就会立刻返回NULL。对于put操作,队列满时,如果wait = true,它就会一直等待到队列不满;如果wait = false,那么它就不会顾及capacity,而直接向队列中存储,导致size > capacity。

3.为了防止播放结束时发生死锁,设置两个接口来解除所有的get和put操作的等待。这一点考虑到解码器解码完毕后,播放器却一直等待。

4.以上所有情况都是是对于有用的数据。而对于回收数据队列,所有的put和get操作只保证线程安全,而不会等待。它没有最大容量,所有的put操作都会在得到锁之后立刻执行。所有的get操作也会在得到线程锁之后立刻执行,如果没有回收数据,立刻返回NULL。

5.通过discardAll(void (*discardCallback)(T))方法可以将所有的有用数据一次性放到回收数据中,并且还可以传递一个函数指针,对所有的有用数据进行回收处理,之后再放入回收队列。这是为了seek操作考虑的,因为seek时要放弃所有已经解码好的数据。

template <class T>
class BlockRecyclerQueue {
public:// if size == -1, then we don't limit the size of data queue, and all the put option will not wait.BlockRecyclerQueue(int capacity = -1);~BlockRecyclerQueue();int getCapacity();int getSize();// put a element, if wait = true, put option will wait until the length of data queue is less than specified size.void put(T t, bool wait = true);// get a element, if wait = true, it will wait until the data queue is not empty. If wait = false, it will return NULL if the data queue is empty.// It will still return NULL even wait = true, in this case, it must be someone call notifyWaitGet() but the data queue is still empty.T get(bool wait = true);void putToUsed(T t);T getUsed();void discardAll(void (*discardCallback)(T));// notify all the put option to not wait. This will cause put option succeed immediatelyvoid notifyWaitPut();// notify all the get option to return immediately. if data queue is still empty, get option will return a NULL.void notifyWaitGet();private:int capacity = 0;mutex queueMu;mutex usedQueueMu;condition_variable notFullSignal;condition_variable notEmptySignal;list<T> queue;list<T> usedQueue;bool allowNotifyPut = false;bool allowNotifyGet = false;};

2. 解码器实现

解码部分还是使用FFmpeg。解码过程和解码音频过程大同小异。

首先,我们肯定需要两个线程来分别解码音频和视频。

其次,还需要一个线程来读取文件,之前我们在解码音频时将从文件中读取packet和将packet解码为frame的过程放在同一个线程中执行,因为音频文件我们只关注音频流。现在我们要将读packet这个操作单独放在一个线程里,然后解码器要维护两个队列,来分别存放音频的AVPacket和视频的AVPacket,这两个队列就可以使用之前的BlockRecyclerQueue。这相当于,读文件线程是生产者,而音频解码线程和视频解码线程都是消费者。具体代码可以查看VideoFileDecoder.cpp。

需要注意的是,seek操作也是放在解码器中进行的,因为seek需要对媒体文件进行操作。在seek时,同样要将之前所有已经读出的AVPacket抛弃。

由于文件解码出的编码格式会不一样,因此我们需要FFmpeg的swr_convert来转码音频数据,用sws_scale转码视频数据。

3. 播放控制

我向外提供了一个播放器的统一操作接口:VideoPlayController.cpp,同时它还负责通知上层播放进度、管理音视频播放器和解码器、管理已解码好的数据等。因此它的声明如下:

class VideoPlayController: public IMediaDataReceiver, public IAudioFrameProvider, public IVideoFrameProvider

它实现了三个接口,可以接受解码器解码好的数据,并且向音视频播放器分别提供音频数据和视频数据。

4. 音视频同步

由于通常音频帧率要比视频帧率高很多,一般视频中的音频采样率多为44.1kHz或48kHz,而视频一般是25fps。

音视频同步通常有两种方式:

  1. 以音频时间基准播放视频,这是由于音频帧率更高。
  2. 以额外的时钟对音视频进行同步。

一般来说,额外时钟的方式会更好一些,一是因为它的精度高;二是这样一来,如果出现文件中只有视频或者只有音频的情况,适用性也会更高些;三是如果你的音频播放器不是主动请求音频数据的,那么你无论如何都需要一个额外时钟来向音频播放器和视频播放器定时发送数据。不过它的缺点在于多占资源。

我这里使用的是以音频时间为基准,因为OpenSLES是主动请求音频数据的。这样一来每次音频播放器请求数据时,我们可以拿到当前AudioFrame的pts,就可以得知当前的播放进度,也可以以这个播放进度来判断是否向视频播放器发送刷新指令。

自然而然,播放和暂停功能也是通过控制音频播放器的播放暂停来实现的。

音视频同步也放在VideoPlayController.cpp中。音视频同步部分的代码放在AudioFrame *VideoPlayController::getAudioFrame()方法中。

5. 总结

至此,这个播放器的关键部分就理清了。代码请上我的github上查看,链接在博客顶部。不过它仍然有很多问题:

1.某些情况下,退出视频播放会ANR,可能是某个线程进入了死锁或者死等待。

2.现在只能正常播放分辨率较低的视频,因为没有针对硬件加速做优化,导致解码视频过于耗时。测试得出解码一帧1920*1080的视频解码需要差不多70ms。

原文链接:基于FFmpeg的简单Android视频播放器_android ffmpeg播放视频_zuguorui的博客-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/621004.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu20.04安装cuda11.4以及cudnn

系统&#xff1a;ubuntu20.04硬件配置&#xff1a;GPU3080、CPU未知通过《软件和更新》在附加驱动选项中添加了驱动&#xff1a; 1.检查自己电脑支持的cuda nvidia-smi4. 下载cuda11.4.2 wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/c…

TypeScript 从入门到进阶之基础篇(十) 抽象类篇

系列文章目录 TypeScript 从入门到进阶系列 TypeScript 从入门到进阶之基础篇(一) ts基础类型篇TypeScript 从入门到进阶之基础篇(二) ts进阶类型篇TypeScript 从入门到进阶之基础篇(三) 元组类型篇TypeScript 从入门到进阶之基础篇(四) symbol类型篇TypeScript 从入门到进阶…

AI-图片转换绚丽动漫人物-UGATIT

​​​​​​ &#x1f3e1; 个人主页&#xff1a;IT贫道-CSDN博客 &#x1f6a9; 私聊博主&#xff1a;私聊博主加WX好友&#xff0c;获取更多资料哦~ &#x1f514; 博主个人B栈地址&#xff1a;豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录 ​​​​…

【Databend】多表联结,你不会还没有掌握吧!

文章目录 概述和数据准备内连接交叉连接左连接右连接左反和右反连接全连接总结 概述和数据准备 多表联结是两个或多个表的列合并到一个结果集中。Databend 中支持的连接类型有 inner join 、cross join 、natural join 、left join 、right join 、left anti join 、right ant…

SqlAlchemy使用教程(二) 入门示例及通过CoreAPI访问与操作数据库

二、入门示例与基本编程步骤 在第一章中提到&#xff0c;Sqlalchemy提供了两套方法来访问数据库&#xff0c;由于Sqlalchemy 文档杂乱&#xff0c;对于ORM的使用步骤讲解杂乱&#xff0c;SqlAlchemy2.x 与j1.x版本差异也较大&#xff0c;很多介绍SqlAlchemy的文章上来就讲ORM&…

TS2307: Cannot find module ‘./App.vue‘ or its corresponding type declarations.

目录 1. 问题描述2. 解决方案一&#xff1a;VSCode Volar&#xff08;官方推荐&#xff09;3. 解决方案二&#xff1a;WebStorm 2023.2 &#xff08;官方推荐&#xff09;4. 解决方案三&#xff1a;禁用严格类型检查选项&#xff08;不推荐&#xff09;5. 解决方案四&#xff…

大模型实战营Day5 LMDeploy大模型量化部署实践

模型部署 定义 产品形态 计算设备 大模型特点 内存开销大 动态shape 结构简单 部署挑战 设备存储 推理速度 服务质量 部署方案&#xff1a;技术点 &#xff08;模型并行 transformer计算和访存优化 低比特量化 Continuous Batch Page Attention&#xff09;方案&#xff08;…

rpc的正确打开方式|读懂Go原生net/rpc包

前言 大家好&#xff0c;这里是白泽&#xff0c;之前最近在阅读字节跳动开源RPC框架Kitex的源码&#xff0c;分析了如何借助命令行&#xff0c;由一个IDL文件&#xff0c;生成client和server的脚手架代码&#xff0c;也分析了Kitex的日志组件klog。当然Kitex还有许多其他组件&…

RXJS中Subject, BehaviorSubject, ReplaySubject, AsyncSubject的区别?

在RxJS&#xff08;Reactive Extensions for JavaScript&#xff09;中&#xff0c;Subject、BehaviorSubject、ReplaySubject和AsyncSubject都是Observable的变体&#xff0c;它们用于处理观察者模式中的不同场景。以下是它们之间的主要区别&#xff1a; 1、Subject: 是一种特…

在ubuntu平台上安装minecraft

一、获取minecraft启动器安装包 登陆minecraft官网Welcome to the Minecraft Official Site | Minecraft&#xff0c;使用已经购买minecraft的microsoft或者mojang账号登陆。 点击Download Launcher 对于ubuntu系统&#xff0c;使用点击debian版本 此后便会自动下载Minecraft.…

【STM32】FLASH闪存

1 FLASH闪存简介 本节所指STM32内部闪存&#xff0c;即下载程序的时候&#xff0c;程序存储的地方。&#xff08;非易失性&#xff09; STM32F1系列的FLASH包含程序存储器、系统存储器&#xff08;bootloader&#xff0c;不允许修改&#xff09;和选项字节三个部分&#xff0…

Spring Cloud整体架构解析

Spring Cloud整体架构 本文已收录至我的个人网站&#xff1a;程序员波特&#xff0c;主要记录Java相关技术系列教程&#xff0c;共享电子书、Java学习路线、视频教程、简历模板和面试题等学习资源&#xff0c;让想要学习的你&#xff0c;不再迷茫。 Spring Cloud的中文名我们就…

pytorch学习笔记(八)

Sequential 看看搭建了这个能不能更容易管理&#xff0c;CIFAR-10数据集进行 看一下网络模型CIFAR-10模型 1 2 3 4 5 6 7 8 9 输入进过一次卷积&#xff0c;然后经过一次最大池化&#…

YOLOv7基础 | 手把手教你简化网络结构之yolov7.yaml(包括源码+封装步骤+网络结构图)

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。通过下载YOLOv7源码可知&#xff0c;原始的yolov7.yaml文件是拆开写的&#xff0c;比较混乱&#xff0c;也不好理解&#xff0c;并且为后续改进增添了很多困难。基于此种情况&#xff0c;笔者就给大家介绍一种将yolov7.yam…

深入理解 Spark(三)SparkTask 执行与 shuffle 详解

SparkTask 的分发部署与启动流程分析 Spark Action 算子触发 job 提交 Spark 当中 Stage 切分源码详解 Task 的提交与执行 SparkShuffle 机制详解 MapReduceShuffle 全流程深度剖析 MapReduce 全流程执行过程中参与工作的组件以及他们的执行先后顺序&#xff1a;InputFormat …

【AIGC】电影风格的一组绝美高清图提示词解析

实际示例 女人主角&#xff0c;以时尚电影风格为灵感&#xff0c;追求照片般的逼真度&#xff0c;运用伦勃朗式光线&#xff0c;创造奇幻且细节丰富的场景&#xff0c;充满象征意义&#xff0c;使用3D渲染技术达到8K超高清晰度。 分类相关信息主角女人风格时尚电影风格逼真度…

LLM漫谈(三)| 使用Chainlit和LangChain构建文档问答的LLM应用程序

一、Chainlit介绍 Chainlit是一个开源Python包&#xff0c;旨在彻底改变构建和共享语言模型&#xff08;LM&#xff09;应用程序的方式。Chainlit可以创建用户界面&#xff08;UI&#xff09;&#xff0c;类似于由OpenAI开发的ChatGPT用户界面&#xff0c;Chainlit可以开发类似…

5 个适用的免费数据恢复软件【2024 年 版本】

互联网上有许多免费的数据恢复软件产品。有些产品是免费软件&#xff0c;而其他产品则提供该工具的免费试用下载以进行评估。我们列出了2024 年 5 款最佳数据恢复工具 &#xff0c;可以免费下载和试用。 5 个适用的免费数据恢复软件 1.奇客数据恢复&#xff08;Windows和Mac&am…

Apache-Common-Pool2中对象池的使用方式

最近在工作中&#xff0c;对几个产品的技术落地进行梳理。这个过程中发现一些朋友对如何使用Apache的对象池存在一些误解。所以在写作“业务抽象”专题的空闲时间里&#xff0c;本人觉得有必要做一个关于对象池的知识点和坑点讲解。Apache Common-Pool2 组件最重要的功能&#…

中仕公考:2024年上半年中小学教师资格考试(笔试)报名已开始

2024年上半年中小学教师资格考试(笔试)报名工作于1月12日开始&#xff0c;此次笔试在31个省(自治区、直辖市)举办&#xff0c;各省(自治区、直辖市)的报名公告将陆续上网。 个别地区报名截止时间有所差异&#xff0c;上海1月13日报名截止&#xff0c;浙江、天津、河南1月14日截…