播放器
概述
多媒体架构是指为应用程序提供可使用的媒体接口的一整套多媒体系统。该架构包含名为 MediaPlayer 的播放器组件,该播放器可用于控制音频文件的播放。
MediaPlayer 播放器支持不同种类的音频源:
保存在 Flash 中的音频文件
保存在内存缓冲区中的音频数据
Http/Https 数据流
自定义数据源
MediaPlayer 播放器支持以下音频格式:
音频格式 |
描述 |
文件类型 |
|---|---|---|
AAC |
支持标准采样率从 8kHz 到 96kHz 的单声道/立体声内容 |
ADTS raw AAC (.aac) MPEG-4 (.m4a) |
MP3 |
单声道/立体声 8kbps ~320kbps 固定码率(CBR)或可变码率(VBR) |
MP3 (.mp3) |
PCM/WAVE |
8位、16位、24位和浮点线性 PCM,采样率从8kHz到96kHz的原始 PCM 录音 |
WAVE (.wav) |
FLAC |
单声道/立体声(不支持多声道),采样率最高可达到48kHz, 建议使用16位音频 无抖动的24位音频 |
FLAC (.flac) |
Opus |
Ogg (.ogg) |
|
Vorbis |
Ogg (.ogg) |
架构
应用程序根据下图与媒体进行交互:
媒体架构
Playback 状态
媒体的播放是通过状态机进行管理的。播放状态如下图所示:
MediaPlayer 对象支持的播放控制操作如上图所示,其中:
椭圆表示 MediaPlayer 对象的可能状态
带有单箭头的弧线表示驱动对象状态转换的同步方法调用
带有双箭头的弧线表示驱动对象状态转换的异步方法调用
从图中可以看出,某些操作只有在播放器处于特定状态时才有效。如果在错误的状态下执行操作,系统可能会引发其他不良行为。在某些特定状态下调用某些函数可能会触发状态转换,请参阅下表以了解详细信息。
函数 |
有效状态 |
说明 |
|---|---|---|
MediaPlayer_Start |
{Prepared, Started, Paused, PlaybackCompleted } |
在有效状态下调用此函数将切换到Start状态, 而在无效状态下调用将切换到错误状态。 |
MediaPlayer_Stop |
{Prepared, Started, Stopped,Paused, PlaybackCompleted} |
在有效状态下调用此函数将切换到Stopped状态, 而在无效状态下调用将切换到错误状态。 |
MediaPlayer_Pause |
{Started, Paused, PlaybackCompleted} |
在有效状态下调用此函数将切换到Paused状态, 而在无效状态下调用将切换到错误状态。 |
MediaPlayer_Reset |
{Idle, Unprepared, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error |
调用此函数将重置对象。 |
MediaPlayer_GetCurrentTime |
{Idle, Unprepared, Prepared, Started, Paused, Stopped, PlaybackCompleted} |
在有效状态下调用此函数不会改变状态, 而在无效状态下调用将切换到错误状态。 |
MediaPlayer_GetDuration |
{Prepared, Started, Paused, Stopped, PlaybackCompleted} |
在有效状态下调用此函数不会改变状态, 而在无效状态下调用将切换到错误状态。 |
MediaPlayer_IsPlaying |
{Idle, Unprepared, Prepared, Started, Paused, Stopped, PlaybackCompleted} |
在有效状态下调用此函数不会改变状态,而在 无效状态下调用将切换到错误状态。 |
MediaPlayer_SetSource |
{Idle} |
在有效状态下调用此函数不会改变状态, 而在无效状态下调用将切换到错误状态。 |
MediaPlayer_SetDataSource |
{Idle} |
在有效状态下调用此函数将切换到Unprepared状态, 而在无效状态下调用将切换到错误状态。 |
MediaPlayer_Prepare |
{Unprepared, Stopped} |
在有效状态下调用此函数将切换到prepared状态, 而在无效状态下调用将切换到错误状态。 |
MediaPlayer_PrepareAsync |
{Unprepared, Stopped} |
在有效状态下调用此函数将切换到Preparing状态, 而在无效状态下调用将切换到错误状态。 |
MediaPlayer_SetCallbacks |
any |
调用此函数不会改变状态。 |
回调函数
媒体提供了一个注册函数 MediaPlayer_SetCallback(struct MediaPlayer *player, struct MediaPlayerCallback *callbacks) 供应用程序在播放或流媒体时监控状态变化和运行时错误。应用程序需要实现 MediaPlayerCallback 中定义的接口。
相关接口描述如下:
void (*OnMediaPlayerStateChanged)(const struct MediaPlayerCallback *listener, const struct MediaPlayer *player, int state)
通知用于监控播放器状态的变化。
参数 state 是 MediaPlayerStates 中的一种。MediaPlayerStates 的值描述如下:
MediaPlayerStates的值 |
说明 |
|---|---|
MEDIA_PLAYER_IDLE |
表示初始状态 |
MEDIA_PLAYER_PREPARING |
表示播放器正在准备中 |
MEDIA_PLAYER_PREPARED |
表示播放器已经准备就绪 |
MEDIA_PLAYER_STARTED |
表示播放器已经开始播放 |
MEDIA_PLAYER_PAUSED |
表示播放器已经暂停 |
MEDIA_PLAYER_STOPPED |
表示播放器已经停止 |
MEDIA_PLAYER_PLAYBACK_COMPLETE |
表示播放器已经完成播放 |
MEDIA_PLAYER_ERROR |
表示播放器出现错误 |
void (*OnMediaPlayerInfo)(const struct MediaPlayerCallback *listener, const struct MediaPlayer *player, int info, int extra)
通知用于监视一些播放器信息。
参数 info 表示信息的类型,参数 extra 表示信息代码。例如,在缓冲状态下, extra 的值是缓冲数据的百分比。MediaPlayerInfos 的值描述如下:
MediaPlayerInfos的值 |
说明 |
|---|---|
MEDIA_PLAYER_INFO_BUFFERING_START | MediaPlayer 正在内部暂时暂停播放,以便缓冲更多数据 |
|
MEDIA_PLAYER_INFO_BUFFERING_END | MediaPlayer 在填充缓冲区后正恢复播放 |
|
MEDIA_PLAYER_INFO_BUFFERING_INFO_UPDATE | MediaPlayer 更新缓冲数据百分比 |
|
void (*OnMediaPlayerError)(const struct MediaPlayerCallback *listener, const struct MediaPlayer *player, int error, int extra)
通知监视器发生了一些播放器错误。
参数 error 表示错误类型。MediaPlayerErrors 的值描述如下:
MediaPlayerInfos的值 |
说明 |
|---|---|
MEDIA_PLAYER_ERROR_UNKNOWN |
表示播放器发生错误 |
StreamSource 结构体
媒体支持播放客户实现的数据源。应用程序需要实现结构体 StreamSource 中定义的接口。
相关接口描述如下:
rt_status_t (*CheckPrepared)(const StreamSource *source);
检查数据源初始化是否成功。
ssize_t (*ReadAt)(const StreamSource *source, off_t offset, void *data, size_t size);
从客户数据源读取数据,并返回读取的字节数;如果失败,则返回错误值。
rt_status_t (*GetLength)(const StreamSource *source, off_t *size);
获取数据源的完整长度。
流缓冲原理
对于 HTTP 流媒体源,网络带宽会影响播放效果。良好的用户体验应包含以下功能:
当下载速度过慢时,应用程序需要暂停播放并进行提示(例如:弹出一些对话框显示下载百分比)。
当某部分数据下载完成时,应用程序需要恢复播放。
MediaPlayer 为 HTTP 流媒体源设置了一个启动水位,直到缓存了足够的音频数据,播放才会开始。应用程序调用 MediaPlayer_Prepare() 或 MediaPlayer_PrepareAsync() 以准备流媒体源。
MediaPlayer_Prepare()是一个同步函数,应用程序可以在它之后调用MediaPlayer_Start()。MediaPlayer_PrepareAsync()是一个异步函数, 应用程序在接收到MediaPlayerCallback.OnMediaPlayerStateChanged(…, …, MEDIA_PLAYER_PREPARED)之前不可以调用MediaPlayer_Start()。
HTTP 流媒体源启动时的缓冲原理
HTTP 流媒体源在启动时的缓冲原理如下图所示:
对于 HTTP 源,MediaPlayer 将持续检查下载的数据,并通过 MediaPlayerCallback.OnMediaPlayerInfo(…, …, MEDIA_PLAYER_INFO_BUFFERING_INFO_UPDATE) 将已下载的百分比更新给监视器。当下载的数据足够时,MediaPlayer 会触发 MediaPlayerCallback.OnMediaPlayerStateChanged (…, …, MEDIA_PLAYER_PREPARED),应用程序即可开始播放。
HTTP 流媒体源播放过程中的缓冲原理
HTTP 流媒体源播放过程中的缓冲原理如下图所示:
在播放过程中,如果剩余的下载数据不足,MediaPlayer 将触发 MediaPlayerCallback.OnMediaPlayerInfo(…, …, MEDIA_PLAYER_INFO_BUFFERING_START) 。应用程序可以更新用户显示界面,向用户指示当前网络状况不佳。在此期间,MediaPlayer 将持续检查下载的数据,并通过 MediaPlayerCallback.OnMediaPlayerInfo(…, …, MEDIA_PLAYER_INFO_BUFFERING_INFO_UPDATE) 将已下载的百分比更新给监视器。当下载的数据足够时,MediaPlayer 将触发 MediaPlayerCallback.OnMediaPlayerInfo(…, …, MEDIA_PLAYER_INFO_BUFFERING_END) ,应用程序即可恢复播放。
接口
概述
媒体提供了一层接口:
API层 |
描述 |
|---|---|
MediaPlayer |
用于应用程序播放音频文件的高级API |
MediaPlayer API
MediaPlayer API 可以执行基本的播放操作,如开始播放、在播放时暂停音频、查询指定播放的信息,以及注册观察者以监控播放状态的变化。
使用 MediaPlayer
MediaPlayer 支持多种不同的媒体来源,如存储在 Flash 中的本地文件、媒体缓冲数据和流媒体。
在创建播放时,需要向 MediaPlayer 传递一个表示资源路径的 URL。
对于文件来源,URL 是存储路径。例如:
char *url = "lfs://1.wav"。对于缓冲来源,URL 以 "buffer://" 开头。例如:
char *url = "buffer://1611496456"( 1611496456 是指向缓冲数据信息的地址)。对于 Http/Https 数据流,URL 以 "http://" 或 "https://" 开头。例如:
char *url = "http://127.0.0.1/2.mp3"或char *url = "https://127.0.0.1/2.mp3"。
// source_buffer 代表一个 audio 源 const unsigned char source_buffer[] = {-1, -15, 76, -128, 41, 63, ...}; char url[64]; memset(url, 0x00, sizeof(url)); int buffer[2] = { sizeof(source_buffer), source_buffer }; sprintf(&url[0], "%s", "buffer://"); sprintf(&url[strlen(url)], "%d", (int)(&buffer)); struct MediaPlayer *player = MediaPlayer_Create(); MediaPlayer_SetSource(player, url);
char *url = "http://127.0.0.1/2.mp3"; struct MediaPlayer *player = MediaPlayer_Create(); MediaPlayer_SetSource(player, url);
在
MediaPlayer_SetSource()之后, 需要通过MediaPlayer_Prepare()或MediaPlayer_PrepareAsync()来准备播放。MediaPlayer_Prepare()是一个同步函数, 可以在之后立即调用MediaPlayer_Start()。MediaPlayer_Prepare(player); MediaPlayer_Start(player);
MediaPlayer_PrepareAsync()是一个异步函数,在收到MediaPlayerCallback.OnMediaPlayerStateChanged(…, …, MEDIA_PLAYER_PREPARED)之前 不可以 调用MediaPlayer_Start()。enum PlayingStatus { IDLE, PREPARING, PREPARED, ... }; void OnPlayerStateChanged(const struct MediaPlayerCallback *listener, const struct MediaPlayer *player, int state) { switch (state) { case MEDIA_PLAYER_PREPARED: { //进入异步准备完成阶段 g_playing_status = PREPARED; break; } ... } MediaPlayer_PrepareAsync(player); g_playing_status = PREPARING; while (g_playing_status != PREPARED) { OsalMSleep(20); } MediaPlayer_Start(player);
当 MediaPlayer 准备完毕后进入 Prepared 状态,这意味着你可以调用
MediaPlayer_Start()让其播放媒体。此时,通过调用MediaPlayer_Start()和MediaPlayer_Pause()等方法,可以在 Started、Paused 和 PlaybackCompleted 状态之间切换。备注
在调用
MediaPlayer_Stop()后,除非重新准备 MediaPlayer,否则不能再次调用MediaPlayer_Start()。在编写与 MediaPlayer 对象交互的代码时,始终要记住状态图,因为在错误状态下调用其方法是常见的错误原因。
暂停播放:
MediaPlayer_Pause(player);
MediaPlayer 可能会消耗宝贵的系统资源。因此,请确保不要长时间持有 MediaPlayer 实例。播放完成后,应始终调用
MediaPlayer_Stop()和MediaPlayer_Reset()释放资源并恢复状态。在调用MediaPlayer_Reset()后,需要通过设置源和调用MediaPlayer_Prepare()来重新初始化它。当播放完成收到
MEDIA_PLAYER_PLAYBACK_COMPLETE之后调用MediaPlayer_Stop()。MediaPlayer_Stop(player);
当播放结束收到
MEDIA_PLAYER_STOPPED之后调用MediaPlayer_Reset()。MediaPlayer_Reset(player);
此外,你必须释放 MediaPlayer,因为当你的活动没有与用户交互时(除非你在后台播放媒体),持有 MediaPlayer 意义不大。当然,当活动恢复或重新启动时,你需要创建一个新的 MediaPlayer 并在恢复播 放之前重新准备它。以下是你应该如何释放并使 MediaPlayer 为空的步骤:
MediaPlayer_Destory(player); player = NULL;
使用 Callback
你可以创建一个 MediaPlayerCallback 实例并将其分配给播放器以监控播放状态的变化。示例如下:
void OnStateChanged(const struct MediaPlayerCallback *listener, const struct MediaPlayer *player, int state)
{
switch (state) {
case MEDIA_PLAYER_PREPARED:
// 进入异步准备完成阶段
break;
case MEDIA_PLAYER_PLAYBACK_COMPLETE:
// 播放完成,需要调用 Stop();
break;
case MEDIA_PLAYER_STOPPED:
// 结束完成,需要调用 Reset();
break;
case MEDIA_PLAYER_PAUSED:
break;
}
}
void OnInfo(const struct MediaPlayerCallback *listener, const struct MediaPlayer *player, int info, int extra)
{
switch (info) {
case MEDIA_PLAYER_INFO_BUFFERING_START:
// 剩余数据低于水位缓冲,应用程序应暂停播放并弹出提示对话框。
break;
case MEDIA_PLAYER_INFO_BUFFERING_END:
// 现在继续播放。
break;
case MEDIA_PLAYER_INFO_BUFFERING_INFO_UPDATE:
// 在缓冲期间提示缓存缓冲区的百分比。
break;
}
}
void OnError(const struct MediaPlayerCallback *listener, const struct MediaPlayer *player, int error, int extra)
{
// 处理播放错误。
}
struct MediaPlayerCallback *callback = (struct MediaPlayerCallback *)OsalMemCalloc(sizeof (struct MediaPlayerCallback), LOG_TAG, __LINE__);
callback->OnMediaPlayerStateChanged = OnStateChanged;
callback->OnMediaPlayerInfo = OnInfo;
callback->OnMediaPlayerError = OnError;
MediaPlayer_SetCallback(player, callback);
记住释放 MediaPlayerCallback 来防止内存泄露:
OsalMemFree(callback);
使用 StreamSource
如果源不是来自固定的 URL,客户可以基于 StreamSource 实现自己的数据源。 .
typedef struct MyStreamSource MyStreamSource;
struct MyStreamSource {
StreamSource base;
char *data;
int data_length; //当前总源长度;对于未知数据长度的源,数据长度会变化,直到最后一个数据获取等于 1。
bool unknown_data_length;
bool last_data_gained;
};
实现 StreamSource 接口:
rt_status_t MyStreamSource_CheckPrepared(const StreamSource *source)
{
if (!source) {
return OSAL_ERR_NO_INIT;
}
MyStreamSource *stream_source = (MyStreamSource *)source;
return stream_source->data ? OSAL_OK : OSAL_ERR_NO_INIT;
}
ssize_t MyStreamSource_ReadAt(const StreamSource *source, off_t offset, void *data, size_t size)
{
if (!source || !data || !size) {
return (ssize_t)OSAL_ERR_INVALID_OPERATION;
}
MyStreamSource *stream_source = (MyStreamSource *)source;
if (offset >= stream_source->data_length) {
if (stream_source->unknown_data_length && !stream_source->last_data_gained) {
return (ssize_t)STREAM_SOURCE_READ_AGAIN;
}
return (ssize_t)STREAM_SOURCE_EOF;
}
if ((stream_source->data_length - offset) < size) {
size = stream_source->data_length - offset;
}
memcpy(data, stream_source->data + offset, (size_t)size);
return size;
}
rt_status_t MyStreamSource_GetLength(const StreamSource *source, off_t *size)
{
if (!source) {
return STREAM_SOURCE_FAIL;
}
MyStreamSource *stream_source = (MyStreamSource *)source;
*size = stream_source->data_length;
if (stream_source->unknown_data_length && !stream_source->last_data_gained) {
return STREAM_SOURCE_UNKNOWN_LENGTH;
}
return OSAL_OK;
}
StreamSource *MyStreamSource_Create(char *data, int length)
{
MyStreamSource *stream_source = OsalMemCalloc(sizeof(MyStreamSource), LOG_TAG, __LINE__);
stream_source->base.CheckPrepared = MyStreamSource_CheckPrepared;
stream_source->base.ReadAt = MyStreamSource_ReadAt;
stream_source->base.GetLength = MyStreamSource_GetLength;
stream_source->data = data;
stream_source->data_length = length;
return (StreamSource *)stream_source;
}
void MyStreamSource_Destroy(MyStreamSource *source)
{
if (!source) {
return;
}
OsalMemFree((void *)source);
}
以下是您可能使用播放器控制 StreamSource 源播放的方法:
const unsigned char source_buffer[] = {-1, -15, 76, -128, 41, 63, ...};
// source_buffer 表示一个音频数据源
StreamSource *stream_source = MyStreamSource_Create(source_buffer, sizeof(source_buffer));
struct MediaPlayer *player = MediaPlayer_Create();
MediaPlayer_SetDataSource(player, stream_source);
MediaPlayer_Prepare(player);
MediaPlayer_Start(player);
...
MediaPlayer_Stop(player);
MediaPlayer_Reset(player);
MyStreamSource_Destroy((MyStreamSource *)stream_source);
MediaPlayer_Destory(player);