多媒体架构

概述

多媒体架构是指为应用程序提供可使用的媒体接口的一整套多媒体系统。该架构包含名为 RTPlayer 的播放器组件,该播放器可用于控制音频文件的播放。

RTPlayer 播放器支持不同种类的音频源:

  • 保存在 Flash 中的音频文件

  • 保存在内存缓冲区中的音频数据

  • Http/Https 数据流

  • 自定义数据源

RTPlayer 播放器支持以下音频格式:

音频格式

描述

文件类型/容器格式

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)

架构

应用程序根据下图与媒体进行交互:

../../_images/media_architecture.svg

媒体架构

Playback 状态

媒体的播放是通过状态机进行管理的。播放状态如下图所示:

../../_images/media_playback_states.svg

RTPlayer 对象支持的播放控制操作如上图所示,其中:

  • 椭圆表示 RTPlayer 对象的可能状态

  • 带有单箭头的弧线表示驱动对象状态转换的同步方法调用

  • 带有双箭头的弧线表示驱动对象状态转换的异步方法调用

从图中可以看出,某些操作只有在播放器处于特定状态时才有效。如果在错误的状态下执行操作,系统可能会引发其他不良行为。在某些特定状态下调用某些函数可能会触发状态转换,请参阅下表以了解详细信息。

函数

有效状态

无效状态

说明

RTPlayer_Start

{Prepared, Started, Paused,

PlaybackCompleted }

{Idle, Unprepared, Stopped,

Error}

在有效状态下调用此函数将切换到Start状态,

而在无效状态下调用此函数将切换到错误状态。

RTPlayer_Stop

{Prepared, Started, Stopped,

Paused, PlaybackCompleted}

{Idle, Unprepared, Error}

在有效状态下调用此函数将切换到Stopped状态,

而在无效状态下调用此函数将切换到错误状态。

RTPlayer_Pause

{Started, Paused,

PlaybackCompleted}

{Idle, Unprepared, Prepared,

Stopped, Error}

在有效状态下调用此函数将切换到Paused状态,

而在无效状态下调用此函数将切换到错误状态。

RTPlayer_Reset

{Idle, Unprepared, Prepared,

Started, Paused, Stopped,

PlaybackCompleted, Error}

{}

调用此函数将重置对象。

RTPlayer_GetCurrentTime

{Idle, Unprepared, Prepared,

Started, Paused, Stopped,

PlaybackCompleted}

{Error}

在有效状态下调用此函数不会改变状态,而在

无效状态下调用此函数将切换到错误状态。

RTPlayer_GetDuration

{Prepared, Started, Paused,

Stopped, PlaybackCompleted}

PlaybackCompleted}

{Idle, Unprepared, Error}

在有效状态下调用此函数不会改变状态,而在

无效状态下调用此函数将切换到错误状态。

RTPlayer_IsPlaying

{Idle, Unprepared, Prepared,

Started, Paused, Stopped,

PlaybackCompleted}

{Error}

在有效状态下调用此函数不会改变状态,而在

无效状态下调用此函数将切换到错误状态。

RTPlayer_SetSource

{Idle}

{Unprepared, Prepared, Started,

Paused, Stopped,

PlaybackCompleted, Error}

在有效状态下调用此函数不会改变状态,而在

无效状态下调用此函数将切换到错误状态。

RTPlayer_SetDataSource

{Idle}

{Unprepared, Prepared, Started,

Paused, Stopped,

PlaybackCompleted, Error}

在有效状态下调用此函数将切换到Unprepared状态,

而在无效状态下调用此函数将切换到错误状态。

RTPlayer_Prepare

{Unprepared, Stopped}

{Idle, Prepared, Started,

Paused, PlaybackCompleted,

Error}

在有效状态下调用此函数将切换到prepared状态,

而在无效状态下调用此函数将切换到错误状态。

RTPlayer_PrepareAsync

{Unprepared, Stopped}

{Idle, Prepared, Started,

Paused, PlaybackCompleted,

Error}

在有效状态下调用此函数将切换到Preparing状态,

而在无效状态下调用此函数将切换到错误状态。

RTPlayer_SetCallbacks

any

{}

调用此函数不会改变状态。

回调函数

媒体提供了一个注册函数 RTPlayer_SetCallback(struct RTPlayer *player, struct RTPlayerCallback *callbacks) 供应用程序在播放或流媒体时监控状态变化和运行时错误。应用程序需要实现 RTPlayerCallback 中定义的接口。

相关接口描述如下:

void (*OnRTPlayerStateChanged)(const struct RTPlayerCallback *listener, const struct RTPlayer *player, int state)

通知用于监控播放器状态的变化。

参数 state 是 RTPlayerStates 中的一种。RTPlayerStates 的值描述如下:

RTPlayerStates的值

说明

RTPLAYER_IDLE

表示初始状态

RTPLAYER_PREPARING

表示播放器正在准备中

RTPLAYER_PREPARED

表示播放器已经准备就绪

RTPLAYER_STARTED

表示播放器已经开始播放

RTPLAYER_PAUSED

表示播放器已经暂停

RTPLAYER_STOPPED

表示播放器已经停止

RTPLAYER_PLAYBACK_COMPLETE

表示播放器已经完成播放

RTPLAYER_ERROR

表示播放器出现错误

void (*OnRTPlayerInfo)(const struct RTPlayerCallback *listener, const struct RTPlayer *player, int info, int extra)

通知用于监视一些播放器信息。

参数 info 表示信息的类型,参数 extra 表示信息代码。例如,在缓冲状态下, extra 的值是缓冲数据的百分比。RTPlayerInfos的值描述如下:

RTPlayerInfos的值

说明

RTPLAYER_INFO_BUFFERING_START

RTPlayer 正在内部暂时暂停播放,以便缓冲更多数据

RTPLAYER_INFO_BUFFERING_END

RTPlayer 在填充缓冲区后正恢复播放

RTPLAYER_INFO_BUFFERING_INFO_UPDATE

RTPlayer 更新缓冲数据百分比

void (*OnRTPlayerError)(const struct RTPlayerCallback *listener, const struct RTPlayer *player, int error, int extra)

通知监视器发生了一些播放器错误。

参数 error 表示错误类型。RTPlayerErrors 的值描述如下:

RTPlayerInfos的值

说明

RTPLAYER_ERROR_UNKNOWN

表示播放器发生错误

RTDataSource 结构体

媒体支持播放客户实现的数据源。应用程序需要实现结构体 RTDataSource 中定义的接口。

相关接口描述如下:

rt_status_t (*CheckPrepared)(const RTDataSource *source);
  1. 检查数据源初始化是否成功。

    ssize_t (*ReadAt)(const RTDataSource *source, off_t offset, void *data, size_t size);
    
  2. 从客户数据源读取数据,并返回读取的字节数;如果失败,则返回错误值。

    rt_status_t (*GetLength)(const RTDataSource *source, off_t *size);
    
  3. 获取数据源的完整长度。

流缓冲原理

对于 HTTP 流媒体源,网络带宽会影响播放效果。良好的用户体验应包含以下功能:

  • 当下载速度过慢时,应用程序需要暂停播放并进行提示(例如:弹出一些对话框显示下载百分比)。

  • 当某部分数据下载完成时,应用程序需要恢复播放。

RTPlayer 为 HTTP 流媒体源设置了一个启动水位,直到缓存了足够的音频数据,播放才会开始。应用程序调用 RTPlayer_Prepare()RTPlayer_PrepareAsync() 以准备流媒体源。

  • RTPlayer_Prepare() 是一个同步函数,应用程序可以在它之后调用 RTPlayer_Start()

  • RTPlayer_PrepareAsync() 是一个异步函数, 应用程序在接收到 RTPlayerCallback.OnRTPlayerStateChanged(…, …, RTPLAYER_PREPARED) 之前不可以调用 RTPlayer_Start()

HTTP 流媒体源启动时的缓冲原理

HTTP 流媒体源在启动时的缓冲原理如下图所示:

../../_images/media_http_source_stream_buffering_principle_during_starting.svg

对于 HTTP 源,RTPlayer 将持续检查下载的数据,并通过 RTPlayerCallback.OnRTPlayerInfo(…, …, RTPLAYER_INFO_BUFFERING_INFO_UPDATE) 将已下载的百分比更新给监视器。当下载的数据足够时,RTPlayer 会触发 RTPlayerCallback.OnRTPlayerStateChanged (…, …, RTPLAYER_PREPARED),应用程序即可开始播放。

HTTP 流媒体源播放过程中的缓冲原理

HTTP 流媒体源播放过程中的缓冲原理如下图所示:

../../_images/media_http_source_stream_buffering_principle_during_playing.svg

在播放过程中,如果剩余的下载数据不足,RTPlayer 将触发 RTPlayerCallback.OnRTPlayerInfo(…, …, RTPLAYER_INFO_BUFFERING_START) 。应用程序可以更新用户显示界面,向用户指示当前网络状况不佳。在此期间,RTPlayer 将持续检查下载的数据,并通过 RTPlayerCallback.OnRTPlayerInfo(…, …, RTPLAYER_INFO_BUFFERING_INFO_UPDATE) 将已下载的百分比更新给监视器。当下载的数据足够时,RTPlayer 将触发 RTPlayerCallback.OnRTPlayerInfo(…, …, RTPLAYER_INFO_BUFFERING_END) ,应用程序即可恢复播放。

接口

概述

媒体提供了一层接口:

API层

描述

RTPlayer

用于应用程序播放音频文件的高级API

RTPlayer API

RTPlayer API 可以执行基本的播放操作,如开始播放、在播放时暂停音频、查询指定播放的信息,以及注册观察者以监控播放状态的变化。

使用 RTPlayer

RTPlayer 支持多种不同的媒体来源,如存储在 Flash 中的本地文件、媒体缓冲数据和流媒体。

  1. 在创建播放时,需要向 RTPlayer 传递一个表示资源路径的 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 RTPlayer *player = RTPlayer_Create();
    RTPlayer_SetSource(player, url);
    
  2. RTPlayer_SetSource() 之后, 需要通过 RTPlayer_Prepare()RTPlayer_PrepareAsync() 来准备播放。

    • RPlayer_Prepare() 是一个同步函数, 可以在之后立即调用 RTPlayer_Start()

      RTPlayer_Prepare(player);
      RTPlayer_Start(player);
      
    • RTPlayer_PrepareAsync() 是一个异步函数,在收到 RTPlayerCallback.OnRTPlayerStateChanged(…, …, RTPLAYER_PREPARED) 之前 不可以 调用 RTPlayer_Start()

      enum PlayingStatus {
         IDLE,
         PREPARING,
         PREPARED,
         ...
      };
      void OnPlayerStateChanged(const struct RTPlayerCallback *listener, const struct RTPlayer *player, int state)
      {
         switch (state) {
         case RTPLAYER_PREPARED: { //进入异步准备完成阶段
            g_playing_status = PREPARED;
            break;
         }
         ...
      }
      RTPlayer_PrepareAsync(player);
      g_playing_status = PREPARING;
      while (g_playing_status != PREPARED) {
         OsalMSleep(20);
      }
      RTPlayer_Start(player);
      
  3. 当 RTPlayer 准备完毕后进入 Prepared 状态,这意味着你可以调用 RTPlayer_Start() 让其播放媒体。此时,通过调用 RTPlayer_Start()RTPlayer_Pause() 等方法,可以在 Started、Paused 和 PlaybackCompleted 状态之间切换。

    备注

    在调用 RTPlayer_Stop() 后,除非重新准备 RTPlayer,否则不能再次调用 RTPlayer_Start()

  4. 在编写与 RTPlayer 对象交互的代码时,始终要记住状态图,因为在错误状态下调用其方法是常见的错误原因。

    暂停播放:

    RTPlayer_Pause(player);
    

    RTPlayer 可能会消耗宝贵的系统资源。因此,请确保不要长时间持有 RTPlayer 实例。播放完成后,应始终调用 RTPlayer_Stop()RTPlayer_Reset() 释放资源并恢复状态。在调用 RTPlayer_Reset() 后,需要通过设置源和调用 RTPlayer_Prepare() 来重新初始化它。

    • 当播放完成收到 RTPLAYER_PLAYBACK_COMPLETE 之后调用 RTPlayer_Stop()

      RTPlayer_Stop(player);
      
    • 当播放结束收到 RTPLAYER_STOPPED 之后调用 RTPlayer_Reset()

      RTPlayer_Reset(player);
      

    此外,你必须释放 RTPlayer,因为当你的活动没有与用户交互时(除非你在后台播放媒体),持有 RTPlayer 意义不大。当然,当活动恢复或重新启动时,你需要创建一个新的 RTPlayer 并在恢复播 放之前重新准备它。以下是你应该如何释放并使 RTPlayer 为空的步骤:

    RTPlayer_Destory(player);
    player = NULL;
    

使用 Callback

你可以创建一个 RTPlayerCallback 实例并将其分配给播放器以监控播放状态的变化。示例如下:

void OnStateChanged(const struct RTPlayerCallback *listener, const struct RTPlayer *player, int state)
{
   switch (state) {
      case RTPLAYER_PREPARED:
         // 进入异步准备完成阶段
         break;
      case RTPLAYER_PLAYBACK_COMPLETE:
         // 播放完成,需要调用 Stop();
         break;
      case RTPLAYER_STOPPED:
         // 结束完成,需要调用 Reset();
         break;
      case RTPLAYER_PAUSED:
         break;
   }
}
void OnInfo(const struct RTPlayerCallback *listener, const struct RTPlayer *player, int info, int extra)
{
   switch (info) {
      case RTPLAYER_INFO_BUFFERING_START:
         // 剩余数据低于水位缓冲,应用程序应暂停播放并弹出提示对话框。
         break;
      case RTPLAYER_INFO_BUFFERING_END:
         // 现在继续播放。
         break;
      case RTPLAYER_INFO_BUFFERING_INFO_UPDATE:
         // 在缓冲期间提示缓存缓冲区的百分比。
         break;
   }
}
void OnError(const struct RTPlayerCallback *listener, const struct RTPlayer *player, int error, int extra)
{
   // 处理播放错误。
}
struct RTPlayerCallback *callback = (struct RTPlayerCallback *)OsalMemCalloc(sizeof (struct RTPlayerCallback), LOG_TAG, __LINE__);
callback->OnRTPlayerStateChanged = OnStateChanged;
callback->OnRTPlayerInfo = OnInfo;
callback->OnRTPlayerError = OnError;
RTPlayer_SetCallback(player, callback);

记住释放 RTPlayerCallback 来防止内存泄露:

OsalMemFree(callback);

使用 RTDataSource

如果源不是来自固定的 URL,客户可以基于 RTDataSource 实现自己的数据源。 .

typedef struct MyDataSource MyDataSource;
struct MyDataSource {
   RTDataSource base;
   char *data;
   int data_length; //当前总源长度;对于未知数据长度的源,数据长度会变化,直到最后一个数据获取等于1。
   bool unknown_data_length;
   bool last_data_gained;
};

实现 RTDataSource 接口:

rt_status_t MyDataSource_CheckPrepared(const RTDataSource *source)
{
   if (!source) {
      return OSAL_ERR_NO_INIT;
   }
   MyDataSource *data_source = (MyDataSource *)source;
   return data_source->data ? OSAL_OK : OSAL_ERR_NO_INIT;
}
ssize_t MyDataSource_ReadAt(const RTDataSource *source, off_t offset, void *data, size_t size)
{
   if (!source || !data || !size) {
      return (ssize_t)OSAL_ERR_INVALID_OPERATION;
   }
   MyDataSource *data_source = (MyDataSource *)source;
   if (offset >= data_source->data_length) {
      if (data_source->unknown_data_length && !data_source->last_data_gained) {
         return (ssize_t)RTDATA_SOURCE_READ_AGAIN;
      }
      return (ssize_t)RTDATA_SOURCE_EOF;
   }
   if ((data_source->data_length - offset) < size) {
      size = data_source->data_length - offset;
   }
   memcpy(data, data_source->data + offset, (size_t)size);
   return size;
}
rt_status_t MyDataSource_GetLength(const RTDataSource *source, off_t *size)
{
   if (!source) {
      return RTDATA_SOURCE_FAIL;
   }
   MyDataSource *data_source = (MyDataSource *)source;
   *size = data_source->data_length;
   if (data_source->unknown_data_length && !data_source->last_data_gained) {
      return RTDATA_SOURCE_UNKNOWN_LENGTH;
   }
   return OSAL_OK;
}
RTDataSource *MyDataSource_Create(char *data, int length)
{
   MyDataSource *data_source = OsalMemCalloc(sizeof(MyDataSource), LOG_TAG, __LINE__);
   data_source->base.CheckPrepared = MyDataSource_CheckPrepared;
   data_source->base.ReadAt = MyDataSource_ReadAt;
   data_source->base.GetLength = MyDataSource_GetLength;
   data_source->data = data;
   data_source->data_length = length;
   return (RTDataSource *)data_source;
}
void MyDataSource_Destroy(MyDataSource *source)
{
   if (!source) {
      return;
   }
   OsalMemFree((void *)source);
}

以下是您可能使用播放器控制 RTDataSource 源播放的方法:

const unsigned char source_buffer[] = {-1, -15, 76, -128, 41, 63, ...};
  // source_buffer 表示一个音频数据源
RTDataSource *data_source = MyDataSource_Create(source_buffer, sizeof(source_buffer));
struct RTPlayer *player = RTPlayer_Create();
RTPlayer_SetDataSource(player, data_source);
RTPlayer_Prepare(player);
RTPlayer_Start(player);
...
RTPlayer_Stop(player);
RTPlayer_Reset(player);
MyDataSource_Destroy((MyDataSource *)data_source);
RTPlayer_Destory(player);