广播的业务还是挺好实现的,但业务链条比较长,作为练手项目绝对不错,主要涉及到几个点:
1、音频数据采集;
2、音频数据编码;
3、媒体流组包;
4、 (组播)UDP Socket服务器和客户端,socket接收和发送实现;
5、音频抖动缓冲区,及音频播放器实现;
逻辑框图:
从业务层面看,每个广播都可以通过广播按钮给同组的其他广播喊话;
从网络层面来看,每个广播都有一个信令监听端口,监听广播发起、广播结束的通知;
被喊话的广播接收到喊话通知后,开一个udp端口,接收广播的音频数据包;
业务框图:
录制和播放器参考pjsip的audiotest.c的代码实现;
录制:
//broadcast_record.c
/*
录制wav,编码? 通过组播发送出去
组播接收wav, 解码?播放wav
*/
#include <pjmedia-audiodev/audiodev.h>
#include <pjmedia.h>
#include <pjlib.h>
#include <pjlib-util.h>
#include "broadcast_app.h"
#define THIS_FILE "broadcast_record.c"
static pj_pool_t *pool = NULL;
static pjmedia_aud_param param;
static pjmedia_aud_stream *strm = NULL;
static unsigned playback_lat = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
static unsigned capture_lat = PJMEDIA_SND_DEFAULT_REC_LATENCY;
static volatile RecordCallback recordCallback = NULL;
int stop_record(void)
{
recordCallback = NULL;
if (strm) {
pjmedia_aud_stream_stop(strm);
pjmedia_aud_stream_destroy(strm);
}
if (pool){
pj_pool_release(pool);
}
strm = NULL;
pool = NULL;
return 0;
}
static pj_status_t wav_rec_cb(void *user_data, pjmedia_frame *frame)
{
//回调函数
//return pjmedia_port_put_frame((pjmedia_port*)user_data, frame);
pj_int16_t *pcm_in = (pj_int16_t*)frame->buf;
//编码?
//发送?
if (recordCallback != NULL){
recordCallback(user_data, pcm_in, (int)frame->size, frame->timestamp.u32.lo);
}
return 0;
}
int start_record(void *user_data, RecordCallback callback)
{
pj_status_t status;
pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), "wav",
1000, 1000, NULL);
status = pjmedia_aud_dev_default_param(0, ¶m);
if (status != PJ_SUCCESS) {
printf("pjmedia_aud_dev_default_param()", status);
goto on_return;
}
param.dir = PJMEDIA_DIR_CAPTURE;
param.clock_rate = 16000;
param.samples_per_frame = 320;
param.channel_count = 1;
param.bits_per_sample = 16;
/* Latency settings */
param.flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
param.input_latency_ms = capture_lat;
param.output_latency_ms = playback_lat;
if (strm) {
pjmedia_aud_stream_stop(strm);
pjmedia_aud_stream_destroy(strm);
}
status = pjmedia_aud_stream_create(¶m, &wav_rec_cb, NULL, user_data,
&strm);
if (status != PJ_SUCCESS) {
printf("Error opening the sound device", status);
goto on_failed;
}
status = pjmedia_aud_stream_start(strm);
if (status != PJ_SUCCESS) {
printf("Error starting the sound device", status);
goto on_failed;
}
//增加回调函数
recordCallback = callback;
PJ_LOG(3,(THIS_FILE, "Recording started"));
goto on_return;
on_failed:
if (strm) {
pjmedia_aud_stream_stop(strm);
pjmedia_aud_stream_destroy(strm);
}
if (pool){
pj_pool_release(pool);
}
on_return:
return 0;
}
播放器:
//broadcast_play.c
#include <pjmedia-audiodev/audiodev.h>
#include <pjmedia.h>
#include <pjlib.h>
#include <pjlib-util.h>
#include "broadcast_app.h"
#define THIS_FILE "broadcast_play.c"
static pj_pool_t *pool = NULL;
static pjmedia_aud_param param;
static pjmedia_aud_stream *strm = NULL;
static unsigned playback_lat = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
static unsigned capture_lat = PJMEDIA_SND_DEFAULT_REC_LATENCY;
static volatile RecordCallback playCallback = NULL;
static pj_status_t wav_play_cb(void *user_data, pjmedia_frame *frame)
{
//return pjmedia_port_get_frame((pjmedia_port*)user_data, frame);
if (playCallback != NULL){
playCallback(user_data, frame->buf, (int)frame->size, frame->timestamp.u32.lo);
}
return 0;
}
int stop_play(void){
playCallback = NULL;
if (strm) {
pjmedia_aud_stream_stop(strm);
pjmedia_aud_stream_destroy(strm);
}
if (pool){
pj_pool_release(pool);
}
return 0;
}
int start_play(void *user_data, PlayCallback callback)
{
pj_status_t status;
if (pool == NULL){
pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), "play",
1000, 1000, NULL);
}
status = pjmedia_aud_dev_default_param(0, ¶m);
if (status != PJ_SUCCESS) {
printf("pjmedia_aud_dev_default_param()", status);
goto on_return;
}
param.dir = PJMEDIA_DIR_PLAYBACK;
param.clock_rate = 16000;
param.samples_per_frame = 320;
param.channel_count = 1;
param.bits_per_sample = 16;
/* Latency settings */
param.flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
param.input_latency_ms = capture_lat;
param.output_latency_ms = playback_lat;
status = pjmedia_aud_stream_create(¶m, NULL, &wav_play_cb, user_data,
&strm);
if (status != PJ_SUCCESS) {
printf("Error opening the sound device", status);
goto on_failed;
}
status = pjmedia_aud_stream_start(strm);
if (status != PJ_SUCCESS) {
printf("Error starting the sound device", status);
goto on_failed;
}
playCallback = callback;
PJ_LOG(3,(THIS_FILE, "play started,strm:%08x",strm));
goto on_return;
on_failed:
if (strm) {
pjmedia_aud_stream_stop(strm);
pjmedia_aud_stream_destroy(strm);
}
if (pool){
pj_pool_release(pool);
}
on_return:
return 0;
}
头文件:
//broadcast_app.h
#ifndef __BROADCAST_APP__
#define __BROADCAST_APP__
#include <unistd.h>
#ifdef __cplusplus
extern "C"{
#endif
typedef int (*PlayCallback)(void *user_data,uint8_t *rawData, int len, uint32_t timestamp);
typedef int (*RecordCallback)(void *user_data,uint16_t *rawData, int len, uint32_t timestamp);
int start_play(void *user_data, PlayCallback callback);
int stop_play(void);
int start_record(void *user_data, RecordCallback callback);
int stop_record(void);
#ifdef __cplusplus
}
#endif
#endif//__BROADCAST_APP__