AirplayLibrary项目编译出Airplay.dll动态库,对外提供的接口函数如下:
//========================================
//启动 airplay 服务;
//friendname -- airplay服务的名称;
//width -- 显示设备的宽度;
//height -- 显示设备的高度;
//cb -- airplay 回调操作的集合;
//========================================
int XinDawn_StartMediaServer(char *friendname, int width, int height, airplay_callbacks_t *cb);
//========================================
//停止 airplay 服务;
//========================================
void XinDawn_StopMediaServer();
所以,AirplaySdkExample项目引用了Airplay.dll动态库之后,只需要调用
XinDawn_StartMediaServer()函数就可以启动airplay服务器,那么,airplay接收到音视频数据的时候,就调用airplay_callbacks_t *cb参数配置的回调函数进行操作。
那么,在Airplay.dll中的airplay服务,就可以调用当前AirplaySdkExample项目定义的函数。例如airplay_callbacks_t回调函数的定义如下:
//========================================
//airplay 的回调函数集合;
//========================================
struct airplay_callbacks_s
{
void *cls;
/* Compulsory callback functions */
void(*AirPlayPlayback_Open) (void *cls, char *url, float fPosition);
void(*AirPlayPlayback_Play) (void *cls);
void(*AirPlayPlayback_Pause)(void *cls);
void(*AirPlayPlayback_Stop) (void *cls);
void(*AirPlayPlayback_Seek)(void *cls, long fPosition);
void(*AirPlayPlayback_SetVolume)(void *cls, int volume);
void(*AirPlayPlayback_ShowPhoto)(void *cls, unsigned char *data, long long size);
long(*AirPlayPlayback_GetDuration)(void *cls);
long(*AirPlayPlayback_GetPostion)(void *cls);
int(*AirPlayPlayback_IsPlaying)(void *cls);
int(*AirPlayPlayback_IsPaused)(void *cls);
void(*AirPlayAudio_Init)(void *cls, int bits, int channels, int samplerate, int isaudio);
void(*AirPlayAudio_Process)(void *cls, const void *buffer, int buflen, double timestamp, uint32_t seqnum);
void(*AirPlayAudio_destroy)(void *cls);
void(*AirPlayAudio_SetVolume)(void *cls, int volume);//1-100
void(*AirPlayAudio_SetMetadata) (void *cls, const void *buffer, int buflen);
void(*AirPlayAudio_SetCoverart)(void *cls, const void *buffer, int buflen);
void(*AirPlayAudio_Flush)(void *cls);
void(*AirPlayMirroring_Play)(void *cls, int width, int height, const void *buffer, int buflen, int payloadtype, double timestamp);
void(*AirPlayMirroring_Process)(void *cls, const void *buffer, int buflen, int payloadtype, double timestamp);
void(*AirPlayMirroring_Stop)(void *cls);
};
那么,在 start_airplay()函数中,启动airplay服务的时候,有配置:
ao.AirPlayMirroring_Process = AirPlayOutputFunctions::mirroring_process;
然后,进入XinDawn_StartMediaServer()函数,有:
raop_cbs.mirroring_ process = cb->AirPlayMirroring_Play;
...
raop = raop_init(10, &raop_cbs, g_pem_key, NULL);
进入raop_init()函数,有:
memcpy(&raop->callbacks, callbacks, sizeof(raop_callbacks_t));
此时,把raop_cbs对象配置的所有回调函数,都拷贝到raop->callbacks中。其中,raop就是负责音视频数据接收的对象。
当airplay模块接收到镜像数据的时候,调用的操作如下:
airplay->callbacks.mirroring_play(airplay->callbacks.cls, 0, 0, p_buffer, d_size+1, d_type, 0.0);
此时,通过函数指针mirroring_ process,调用了AirPlayOutputFunctions::mirroring_process()函数。
最终,在AirplaySdkExample项目中,可以定义AirPlayOutputFunctions::mirroring_process()函数接收airplay的镜像视频数据,就可以通过ffmpeg和SDL进行视频帧的解码显示。
Airplay.dll中采集到的airplay音视频数据,都存放到一个数据队列中,如下:
typedef struct __xdw_air_decoder_q
{
//存放视频帧的队列;
struct xdw_q_head video_pkt_q;
//存放音频帧的队列;
struct xdw_q_head audio_pkt_q;
} xdw_air_decoder_q;
那么,在Airplay.dll采集到视频数据,存放到 video_pkt_q 队列中,AirplaySdkExample项目中的video_thread_loop()函数就从对应的 video_pkt_q 队列中取出视频帧,然后,使用SDL框架显示该视频帧。
视频镜像的接口函数如下:
(1) 在start_airplay()函数中操作如下:
//镜像的操作;
ao.AirPlayMirroring_Play = AirPlayOutputFunctions::mirroring_play;
ao.AirPlayMirroring_Process = AirPlayOutputFunctions::mirroring_process;
ao.AirPlayMirroring_Stop = AirPlayOutputFunctions::mirroring_stop;
最终,在XinDawn_StartMediaServer()函数中,把这些回调函数,注册给raop对象。
那么,当接收视频帧的时候,调用:
airplay->callbacks.mirroring_process(airplay->callbacks.cls, p_buffer, d_size, d_type, 0.0);
此时,调用AirPlayOutputFunctions::mirroring_process()函数处理。
在该函数中,把数据封装成 H264 数据元,存放到视频队列中。最终, 由 video_thread_loop() 线程进行处理。
在VideoSource::AirPlayOutputFunctions::audio_init()函数中,创建SDL播放音频的对象:
wanted_spec.callback = AirPlayOutputFunctions::sdl_audio_callback;
而且,这个函数,在 start_airplay()函数中配置:
ao.AirPlayAudio_Init = AirPlayOutputFunctions::audio_init;
最终,在XinDawn_StartMediaServer()函数中配置:
raop_cbs.audio_init = cb->AirPlayAudio_Init;
那么,当 airplay 获取一个音频需要处理的时候,就调用raop_cbs.audio_init()回调函数,初始化一个音频对象。
其中 sdl_audio_callback() 函数是由 SDL 播放音频的回调函数,该函数的逻辑是:
(1) 从 音频队列中获取音频数据;
(2) 使用SDL框架播放音频数据;
那么,airplay获取到一个音频数据帧的时候,操作是:
void VideoSource::AirPlayOutputFunctions::audio_process(void *cls, const void *buffer, int buflen, double timestamp, uint32_t seqnum)
{
...
xdw_q_push(&frm_node->list, &(((VideoSource *)cls)->xdw_decoder_q.audio_pkt_q));
}
}
此时,把音频数据帧存放到xdw_decoder_q.audio_pkt_q音频队列中。
那么,SDL的回调函数sdl_audio_callback(),就可以从音频队列中获取音频数据来处理。
现在分析了前端注册airplay回调函数的操作,和回调函数调用的流程。下面就开始分析airplay协议的服务注册和协议的交互。
更多交流可以QQ 1523520001,备注 airplay