日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

pjmedia系列之媒体设备pjmedia_snd_port

發(fā)布時(shí)間:2023/12/14 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 pjmedia系列之媒体设备pjmedia_snd_port 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在simpleua.c文件,當(dāng)協(xié)商成功call_on_media_update中,會創(chuàng)建音頻設(shè)備對象。

static pjmedia_snd_port *g_snd_port; /* Sound device. */static void call_on_media_update( pjsip_inv_session *inv, pj_status_t status) {}pjmedia_port *media_port;/* Get the media port interface of the audio stream. * Media port interface is basicly a struct containing get_frame() and* put_frame() function. With this media port interface, we can attach* the port interface to conference bridge, or directly to a sound* player/recorder device.*/pjmedia_stream_get_port(g_med_stream, &media_port);/* Create sound port */pjmedia_snd_port_create(inv->pool,PJMEDIA_AUD_DEFAULT_CAPTURE_DEV,PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV,PJMEDIA_PIA_SRATE(&media_port->info),/* clock rate */PJMEDIA_PIA_CCNT(&media_port->info),/* channel count */PJMEDIA_PIA_SPF(&media_port->info), /* samples per frame*/PJMEDIA_PIA_BITS(&media_port->info),/* bits per sample */0,&g_snd_port);status = pjmedia_snd_port_connect(g_snd_port, media_port); }

首先從stream獲取一個media_port的接口實(shí)例,這個實(shí)例是在創(chuàng)建流的時(shí)候創(chuàng)建的。

PJ_DEF(pj_status_t) pjmedia_stream_get_port( pjmedia_stream *stream,pjmedia_port **p_port ) {*p_port = &stream->port;return PJ_SUCCESS; }pjmedia_stream_create() { ...stream->port.put_frame = &put_frame;stream->port.get_frame = &get_frame; ... }

這里實(shí)際上是把stream的兩個回調(diào),設(shè)置到音頻設(shè)備,當(dāng)設(shè)備mic采集到數(shù)據(jù)時(shí),最終調(diào)用put_frame,當(dāng)要播放數(shù)據(jù)時(shí),調(diào)用get_frame獲取。stream中的兩個回調(diào)稍后再分析,繼續(xù)看音頻設(shè)備。

結(jié)構(gòu)體

struct pjmedia_snd_port {int rec_id;int play_id;pj_uint32_t aud_caps;pjmedia_aud_param aud_param;pjmedia_aud_stream *aud_stream;pjmedia_dir dir;pjmedia_port *port;pjmedia_clock_src cap_clocksrc,play_clocksrc;unsigned clock_rate;unsigned channel_count;unsigned samples_per_frame;unsigned bits_per_sample;unsigned options;unsigned prm_ec_options;/* audio frame preview callbacks */void *user_data;pjmedia_aud_play_cb on_play_frame;pjmedia_aud_rec_cb on_rec_frame; };

結(jié)構(gòu)體里有兩個回到函數(shù)指針on_play_frame、on_rec_frame,會指向前面講的stream兩個函數(shù)put_frame和get_frame。?

創(chuàng)建

PJ_DEF(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool,int rec_id,int play_id,unsigned clock_rate,unsigned channel_count,unsigned samples_per_frame,unsigned bits_per_sample,unsigned options,pjmedia_snd_port **p_port) {pjmedia_snd_port_param param;pj_status_t status;pjmedia_snd_port_param_default(&param);/* Normalize rec_id & play_id */if (rec_id < 0)rec_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV;if (play_id < 0)play_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;status = pjmedia_aud_dev_default_param(rec_id, &param.base);if (status != PJ_SUCCESS)return status;param.base.dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;param.base.rec_id = rec_id;param.base.play_id = play_id;param.base.clock_rate = clock_rate;param.base.channel_count = channel_count;param.base.samples_per_frame = samples_per_frame;param.base.bits_per_sample = bits_per_sample;param.options = options;param.ec_options = 0;return pjmedia_snd_port_create2(pool, &param, p_port); }

初始化一些參數(shù),然后調(diào)用?pjmedia_snd_port_create2。

/** Create sound port.*/ PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool,const pjmedia_snd_port_param *prm,pjmedia_snd_port **p_port) {pjmedia_snd_port *snd_port;pj_status_t status;unsigned ptime_usec;PJ_ASSERT_RETURN(pool && prm && p_port, PJ_EINVAL);snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port);PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM);snd_port->dir = prm->base.dir;snd_port->rec_id = prm->base.rec_id;snd_port->play_id = prm->base.play_id;snd_port->clock_rate = prm->base.clock_rate;snd_port->channel_count = prm->base.channel_count;snd_port->samples_per_frame = prm->base.samples_per_frame;snd_port->bits_per_sample = prm->base.bits_per_sample;pj_memcpy(&snd_port->aud_param, &prm->base, sizeof(snd_port->aud_param));snd_port->options = prm->options;snd_port->prm_ec_options = prm->ec_options;snd_port->user_data = prm->user_data;snd_port->on_play_frame = prm->on_play_frame;snd_port->on_rec_frame = prm->on_rec_frame;ptime_usec = prm->base.samples_per_frame * 1000 / prm->base.channel_count /prm->base.clock_rate * 1000;pjmedia_clock_src_init(&snd_port->cap_clocksrc, PJMEDIA_TYPE_AUDIO,snd_port->clock_rate, ptime_usec);pjmedia_clock_src_init(&snd_port->play_clocksrc, PJMEDIA_TYPE_AUDIO,snd_port->clock_rate, ptime_usec);/* Start sound device immediately.* If there's no port connected, the sound callback will return* empty signal.*/status = start_sound_device( pool, snd_port );if (status != PJ_SUCCESS) {pjmedia_snd_port_destroy(snd_port);return status;}*p_port = snd_port;return PJ_SUCCESS; }

?pjmedia_snd_port_create2同樣是初始化一些參數(shù),主要有時(shí)鐘頻率,最后調(diào)用start_sound_device。

static pj_status_t start_sound_device( pj_pool_t *pool,pjmedia_snd_port *snd_port ) {pjmedia_aud_rec_cb snd_rec_cb;pjmedia_aud_play_cb snd_play_cb;pjmedia_aud_param param_copy;pj_status_t status;.../* Use different callback if format is not PCM */if (snd_port->aud_param.ext_fmt.id == PJMEDIA_FORMAT_L16) {snd_rec_cb = &rec_cb;snd_play_cb = &play_cb;} else {snd_rec_cb = &rec_cb_ext;snd_play_cb = &play_cb_ext;}/* Open the device */status = pjmedia_aud_stream_create(&param_copy,snd_rec_cb,snd_play_cb,snd_port,&snd_port->aud_stream);.../* Start sound stream. */if (!(snd_port->options & PJMEDIA_SND_PORT_NO_AUTO_START)) {status = pjmedia_aud_stream_start(snd_port->aud_stream);}return PJ_SUCCESS; }

?由流程可知,真正到pjmedia_aud_stream_create函數(shù)才創(chuàng)建設(shè)備,并且傳入兩個回調(diào)rec_cb和play_cb,這兩個回調(diào)并不是stream的那兩個回調(diào),而是sound_port.c這一層自己實(shí)現(xiàn)的回調(diào),play_cb里面才會調(diào)用stream的回調(diào),也就是多了一層,這點(diǎn)跟transport是一樣的,都是在本層實(shí)現(xiàn)一個回調(diào),然后在調(diào)用上層設(shè)置的回調(diào)。

pjmedia_aud_stream_create的實(shí)現(xiàn)在pjmedia/audiodev.c(注意不是pjmedia-audiodev/audiodev.c)。

/* API: Open audio stream object using the specified parameters. */ PJ_DEF(pj_status_t) pjmedia_aud_stream_create(const pjmedia_aud_param *prm,pjmedia_aud_rec_cb rec_cb,pjmedia_aud_play_cb play_cb,void *user_data,pjmedia_aud_stream **p_aud_strm) {pjmedia_aud_dev_factory *rec_f=NULL, *play_f=NULL, *f=NULL;pjmedia_aud_param param;pj_status_t status;PJ_ASSERT_RETURN(prm && prm->dir && p_aud_strm, PJ_EINVAL);PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);PJ_ASSERT_RETURN(prm->dir==PJMEDIA_DIR_CAPTURE ||prm->dir==PJMEDIA_DIR_PLAYBACK ||prm->dir==PJMEDIA_DIR_CAPTURE_PLAYBACK,PJ_EINVAL);status = lookup_dev(param.rec_id, &rec_f, &index);status = lookup_dev(param.play_id, &play_f, &index);/* Create the stream */status = f->op->create_stream(f, &param, rec_cb, play_cb,user_data, p_aud_strm);if (status != PJ_SUCCESS)return status;/* Assign factory id to the stream */(*p_aud_strm)->sys.drv_idx = f->sys.drv_idx;return PJ_SUCCESS; }

先通過lookup_dev搜索設(shè)備工廠,然后通過工廠創(chuàng)建設(shè)備f->op->create_stream。關(guān)鍵點(diǎn)來了,如何搜索到設(shè)備。

/* Internal: lookup device id */ static pj_status_t lookup_dev(pjmedia_aud_dev_index id,pjmedia_aud_dev_factory **p_f,unsigned *p_local_index) {int f_id, index;if (id < 0) {unsigned i;if (id == PJMEDIA_AUD_INVALID_DEV)return PJMEDIA_EAUD_INVDEV;for (i=0; i<aud_subsys.drv_cnt; ++i) {pjmedia_aud_driver *drv = &aud_subsys.drv[i];if (drv->dev_idx >= 0) {id = drv->dev_idx;make_global_index(i, &id);break;} else if (id==PJMEDIA_AUD_DEFAULT_CAPTURE_DEV && drv->rec_dev_idx >= 0) {id = drv->rec_dev_idx;make_global_index(i, &id);break;} else if (id==PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV && drv->play_dev_idx >= 0) {id = drv->play_dev_idx;make_global_index(i, &id);break;}}if (id < 0) {return PJMEDIA_EAUD_NODEFDEV;}}f_id = GET_FID(aud_subsys.dev_list[id]);index = GET_INDEX(aud_subsys.dev_list[id]);if (f_id < 0 || f_id >= (int)aud_subsys.drv_cnt)return PJMEDIA_EAUD_INVDEV;if (index < 0 || index >= (int)aud_subsys.drv[f_id].dev_cnt)return PJMEDIA_EAUD_INVDEV;*p_f = aud_subsys.drv[f_id].f;*p_local_index = (unsigned)index;return PJ_SUCCESS;}

從這里可以看出,全局變量aud_subsys已經(jīng)存儲了所有的音頻設(shè)備,這里把它找出來即可。那音頻設(shè)備是不是在初始化的時(shí)候就搜索完了呢?aud_subsys全局變量什么時(shí)候賦值的?繼續(xù)分析

在pjmedia-audiodev/audiodev.c中,有個音頻子系統(tǒng)初始化函數(shù)

PJ_DEF(pj_status_t) pjmedia_aud_subsys_init(pj_pool_factory *pf) {unsigned i;pj_status_t status;pjmedia_aud_subsys *aud_subsys = pjmedia_get_aud_subsys();/* Allow init() to be called multiple times as long as there is matching* number of shutdown().*/if (aud_subsys->init_count++ != 0) {return PJ_SUCCESS;}/* Register error subsystem */status = pj_register_strerror(PJMEDIA_AUDIODEV_ERRNO_START,PJ_ERRNO_SPACE_SIZE,&pjmedia_audiodev_strerror);pj_assert(status == PJ_SUCCESS);/* Init */aud_subsys->pf = pf;aud_subsys->drv_cnt = 0;aud_subsys->dev_cnt = 0;/* Register creation functions */ #if PJMEDIA_AUDIO_DEV_HAS_OPENSLaud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_opensl_factory; #endif #if PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNIaud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_android_factory; #endif #if PJMEDIA_AUDIO_DEV_HAS_BB10aud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_bb10_factory; #endif #if PJMEDIA_AUDIO_DEV_HAS_ALSAaud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_alsa_factory; #endif #if PJMEDIA_AUDIO_DEV_HAS_COREAUDIOaud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_coreaudio_factory; #endif #if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIOaud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_pa_factory; #endif #if PJMEDIA_AUDIO_DEV_HAS_WMMEaud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_wmme_factory; #endif #if PJMEDIA_AUDIO_DEV_HAS_BDIMADaud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_bdimad_factory; #endif #if PJMEDIA_AUDIO_DEV_HAS_SYMB_VASaud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_symb_vas_factory; #endif #if PJMEDIA_AUDIO_DEV_HAS_SYMB_APSaud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_aps_factory; #endif #if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDAaud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_symb_mda_factory; #endif #if PJMEDIA_AUDIO_DEV_HAS_WASAPIaud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_wasapi_factory; #endif #if PJMEDIA_AUDIO_DEV_HAS_NULL_AUDIOaud_subsys->drv[aud_subsys->drv_cnt++].create = &pjmedia_null_audio_factory; #endif/* Initialize each factory and build the device ID list */for (i=0; i<aud_subsys->drv_cnt; ++i) {status = pjmedia_aud_driver_init(i, PJ_FALSE);if (status != PJ_SUCCESS) {pjmedia_aud_driver_deinit(i);continue;}}return aud_subsys->dev_cnt ? PJ_SUCCESS : status; }

也就是說,初始化的時(shí)候調(diào)用這個函數(shù),就會根據(jù)根據(jù)編譯宏,把各種音頻類型工廠添加到子系統(tǒng)全局變量,其中我們關(guān)注pjmedia_alsa_factory。而音頻子系統(tǒng)的初始化,是在創(chuàng)建媒體端點(diǎn)的時(shí)候。

PJ_INLINE(pj_status_t) pjmedia_endpt_create(pj_pool_factory *pf,pj_ioqueue_t *ioqueue,unsigned worker_cnt,pjmedia_endpt **p_endpt) {/* This function is inlined to avoid build problem due to circular* dependency, i.e: this function prevents pjmedia's dependency on* pjmedia-audiodev.*/pj_status_t status;/* Sound */status = pjmedia_aud_subsys_init(pf);if (status != PJ_SUCCESS)return status;status = pjmedia_endpt_create2(pf, ioqueue, worker_cnt, p_endpt);if (status != PJ_SUCCESS) {pjmedia_aud_subsys_shutdown();}return status; }

這樣分析后,整個脈絡(luò)就理清了 。初始化的時(shí)候創(chuàng)建媒體端點(diǎn)pjmedia_endpt,同時(shí)初始化了音頻子系統(tǒng),把各種類型的音頻設(shè)備工廠添加到全局變量static pjmedia_aud_subsys aud_subsys;。這樣當(dāng)創(chuàng)建設(shè)備時(shí),就可以遍歷這些工廠,尋找合適的工廠,通過工廠創(chuàng)建設(shè)備實(shí)例。比如alsa類型的設(shè)備在alsa_dev.c

static pjmedia_aud_dev_factory_op alsa_factory_op = {&alsa_factory_init,&alsa_factory_destroy,&alsa_factory_get_dev_count,&alsa_factory_get_dev_info,&alsa_factory_default_param,&alsa_factory_create_stream,&alsa_factory_refresh };

到此,設(shè)備的創(chuàng)建流程基本分析完了。音頻設(shè)備分三層,最底層的是各種設(shè)備類型,比如alsa,再上一層是抽象設(shè)備操作audiodev.c,最上面一層是設(shè)備接口sound_port。

數(shù)據(jù)流

創(chuàng)建完設(shè)備,我們再來分析數(shù)據(jù)流向,以alsa為例,在alsa_dev.c中,會創(chuàng)建播放和采集兩條線程。

static pj_status_t alsa_stream_start (pjmedia_aud_stream *s) {struct alsa_stream *stream = (struct alsa_stream*)s;pj_status_t status = PJ_SUCCESS;stream->quit = 0;if (stream->param.dir & PJMEDIA_DIR_PLAYBACK) {status = pj_thread_create (stream->pool,"alsasound_playback",pb_thread_func,stream,0, //ZERO,0,&stream->pb_thread);if (stream->param.dir & PJMEDIA_DIR_CAPTURE) {status = pj_thread_create (stream->pool,"alsasound_playback",ca_thread_func,stream,0, //ZERO,0,&stream->ca_thread);}return status; }

以播放線程為例,采集線程一樣。

static int pb_thread_func (void *arg) {struct alsa_stream* stream = (struct alsa_stream*) arg;snd_pcm_t* pcm = stream->pb_pcm;int size = stream->pb_buf_size;snd_pcm_uframes_t nframes = stream->pb_frames;void* user_data = stream->user_data;char* buf = stream->pb_buf;pj_timestamp tstamp;int result;pj_bzero (buf, size);tstamp.u64 = 0;snd_pcm_prepare (pcm);while (!stream->quit) {pjmedia_frame frame;frame.type = PJMEDIA_FRAME_TYPE_AUDIO;frame.buf = buf;frame.size = size;frame.timestamp.u64 = tstamp.u64;frame.bit_info = 0;result = stream->pb_cb (user_data, &frame);if (result != PJ_SUCCESS || stream->quit)break;if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)pj_bzero (buf, size);result = snd_pcm_writei (pcm, buf, nframes);if (result == -EPIPE) {PJ_LOG (4,(THIS_FILE, "pb_thread_func: underrun!"));snd_pcm_prepare (pcm);} else if (result < 0) {PJ_LOG (4,(THIS_FILE, "pb_thread_func: error writing data!"));}tstamp.u64 += nframes;}snd_pcm_drain (pcm);TRACE_((THIS_FILE, "pb_thread_func: Stopped"));return PJ_SUCCESS; }

播放線程先通過回調(diào)拿到待播放的音頻數(shù)據(jù)stream->pb_cb ,然后寫到聲卡snd_pcm_writei。pb_cb就是sound_port.c中的play_cb,來看下play_cb的流程。

static pj_status_t play_cb(void *user_data, pjmedia_frame *frame) {pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data;pjmedia_port *port;const unsigned required_size = (unsigned)frame->size;pj_status_t status;port = snd_port->port;status = pjmedia_port_get_frame(port, frame);/* Invoke preview callback */if (snd_port->on_play_frame)(*snd_port->on_play_frame)(snd_port->user_data, frame);return PJ_SUCCESS; }

play_cb做了兩件事

1、通過pjmedia_port* port獲取一幀數(shù)據(jù)

port是snd_port的成員,這個成員是在什么時(shí)候賦值的?回到simpleua.c中,pjmedia_snd_port_create后,還調(diào)用了一個函數(shù)pjmedia_snd_port_connect(g_snd_port, media_port);把stream的media_port傳進(jìn)去。

PJ_DEF(pj_status_t) pjmedia_snd_port_connect( pjmedia_snd_port *snd_port,pjmedia_port *port) {pjmedia_audio_format_detail *afd;.../* Port is okay. */snd_port->port = port;return PJ_SUCCESS; } /*** Get a frame from the port (and subsequent downstream ports).*/ PJ_DEF(pj_status_t) pjmedia_port_get_frame( pjmedia_port *port,pjmedia_frame *frame ) {PJ_ASSERT_RETURN(port && frame, PJ_EINVAL);if (port->get_frame)return port->get_frame(port, frame);else {frame->type = PJMEDIA_FRAME_TYPE_NONE;return PJ_EINVALIDOP;} }

?所以這個port->get_frame就是stream.c中的get_frame。

2、通過pjmedia_snd_port調(diào)用預(yù)覽,實(shí)際上跟蹤會發(fā)現(xiàn),預(yù)覽指針并沒有賦值。

調(diào)用pjmedia_snd_port* snd_port的on_play_frame函數(shù)指針,通過user_data傳遞snd_port。從pjmedia_aud_stream_create函數(shù)可以知道,第4個參數(shù)就是user_data,這個參數(shù)是在start_sound_device傳入的第二個參數(shù)pjmedia_snd_port *snd_port。跟蹤發(fā)現(xiàn),想創(chuàng)建設(shè)備的時(shí)候,為on_play_frame指針賦值為空,那什么時(shí)候有值?

這樣整個播放數(shù)據(jù)流就清楚了。首先在alsa創(chuàng)建播放線程,播放線程通過一系列回調(diào),從stream拿到一幀數(shù)據(jù),然后寫設(shè)備播放,關(guān)鍵是捋清楚多層回調(diào)的關(guān)系。

總結(jié)

以上是生活随笔為你收集整理的pjmedia系列之媒体设备pjmedia_snd_port的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。