实现需求,全志IPC,PJSIP本地预览视频,解码并显示对端视频。先梳理PJSIP本地预览和解码显示流程。
本地预览:默认配置 vid_preview_enable_native 是开启的。
PJ_DEF(void) pjsua_media_config_default(pjsua_media_config *cfg)
{
//....
cfg->vid_preview_enable_native = PJ_TRUE;
}
static pj_status_t create_vid_win(pjsua_vid_win_type type,
const pjmedia_format *fmt,
pjmedia_vid_dev_index rend_id,
pjmedia_vid_dev_index cap_id,
pj_bool_t show,
unsigned wnd_flags,
const pjmedia_vid_dev_hwnd *wnd,
pjsua_vid_win_id *id)
{
pj_bool_t enable_native_preview;
pjsua_vid_win_id wid = PJSUA_INVALID_ID;
pjsua_vid_win *w = NULL;
pjmedia_vid_port_param vp_param;
pjmedia_format fmt_;
pj_status_t status;
unsigned i;
enable_native_preview = pjsua_var.media_cfg.vid_preview_enable_native;
//...
//...
/*
* Determine if the device supports native preview.
*/
status = pjmedia_vid_dev_get_info(cap_id, &vdi);
if (status != PJ_SUCCESS)
goto on_error;
if (enable_native_preview &&
(vdi.caps & PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW))
{
/* Device supports native preview! */
w->is_native = PJ_TRUE;
}
//...
}本地预览视频流程:

视频本地采集,编码后传输:
摄像头采集回调:

on_clock_tick驱动编码,rtp发送。

显示对端视频,收包,解码,然后显示。
channel->stream->transport
/**
* Media channel.
*/
typedef struct pjmedia_vid_channel
{
pjmedia_vid_stream *stream; /**< Parent stream. */
pjmedia_dir dir; /**< Channel direction. */
pjmedia_port port; /**< Port interface. */
unsigned pt; /**< Payload type. */
pj_bool_t paused; /**< Paused?. */
void *buf; /**< Output buffer. */
unsigned buf_size; /**< Size of output buffer. */
pjmedia_rtp_session rtp; /**< RTP session. */
} pjmedia_vid_channel;
/**
* This structure describes media stream.
* A media stream is bidirectional media transmission between two endpoints.
* It consists of two channels, i.e. encoding and decoding channels.
* A media stream corresponds to a single "m=" line in a SDP session
* description.
*/
struct pjmedia_vid_stream
{
pj_pool_t *own_pool; /**< Internal pool. */
pjmedia_endpt *endpt; /**< Media endpoint. */
pjmedia_vid_codec_mgr *codec_mgr; /**< Codec manager. */
pjmedia_vid_stream_info info; /**< Stream info. */
pj_grp_lock_t *grp_lock; /**< Stream lock. */
pjmedia_vid_channel *enc; /**< Encoding channel. */
pjmedia_vid_channel *dec; /**< Decoding channel. */
pjmedia_dir dir; /**< Stream direction. */
void *user_data; /**< User data. */
pj_str_t name; /**< Stream name */
pj_str_t cname; /**< SDES CNAME */
pjmedia_transport *transport; /**< Stream transport. */
}建立数据编码通道: create_channel( pool, stream, PJMEDIA_DIR_ENCODING, info->tx_pt, info, &stream->enc);
建立数据解码通道: create_channel( pool, stream, PJMEDIA_DIR_DECODING, info->rx_pt, info, &stream->dec);
音频 Stream.c (pjmedia\src\pjmedia):static pj_status_t create_channel( pj_pool_t *pool, Stream.c (pjmedia\src\pjmedia): status = create_channel( pool, stream, PJMEDIA_DIR_DECODING, Stream.c (pjmedia\src\pjmedia): status = create_channel( pool, stream, PJMEDIA_DIR_ENCODING, 视频 Vid_stream.c (pjmedia\src\pjmedia):static pj_status_t create_channel( pj_pool_t *pool, Vid_stream.c (pjmedia\src\pjmedia): status = create_channel( pool, stream, PJMEDIA_DIR_DECODING, Vid_stream.c (pjmedia\src\pjmedia): status = create_channel( pool, stream, PJMEDIA_DIR_ENCODING,
/*
* Create stream.
*/
PJ_DEF(pj_status_t) pjmedia_vid_stream_create(
pjmedia_endpt *endpt,
pj_pool_t *pool,
pjmedia_vid_stream_info *info,
pjmedia_transport *tp,
void *user_data,
pjmedia_vid_stream **p_stream)
/*
* Create media channel.
*/
static pj_status_t create_channel( pj_pool_t *pool,
pjmedia_vid_stream *stream,
pjmedia_dir dir,
unsigned pt,
const pjmedia_vid_stream_info *info,
pjmedia_vid_channel **p_channel)
{
/* Create RTP and RTCP sessions: */
{
pjmedia_rtp_session_setting settings;
settings.flags = (pj_uint8_t)((info->rtp_seq_ts_set << 2) |
(info->has_rem_ssrc << 4) | 3);
settings.default_pt = pt;
settings.sender_ssrc = info->ssrc;
settings.peer_ssrc = info->rem_ssrc;
settings.seq = info->rtp_seq;
settings.ts = info->rtp_ts;
status = pjmedia_rtp_session_init2(&channel->rtp, settings);
}
}解码流程:

问题1:
编码发送驱动是由on_clock_tick驱动,那解码显示驱动呢?参考音频的,音频的播放是音频设备的play_cb驱动port的get_frame驱动。
但是视频的解码包显示是由on_clock_tick驱动的。

/* Call sink->put_frame()
* Note that if transmitter_cnt==0, we should still call put_frame()
* with zero frame size, as sink may need to send keep-alive packets
* and get timestamp update.
*/
pj_bzero(&frame, sizeof(frame));
frame.type = PJMEDIA_FRAME_TYPE_VIDEO;
frame.timestamp = *now;
if (frame_rendered) {
frame.buf = sink->put_buf;
frame.size = sink->put_frm_size;
}
status = pjmedia_port_put_frame(sink->port, &frame);
if (frame_rendered && status != PJ_SUCCESS) {
sink->last_err_cnt++;
if (sink->last_err != status ||
sink->last_err_cnt % MAX_ERR_COUNT == 0)
{
if (sink->last_err != status)
sink->last_err_cnt = 1;
sink->last_err = status;
PJ_PERROR(5, (THIS_FILE, status,
"Failed (%d time(s)) to put frame to port %d"
" [%s]!", sink->last_err_cnt,
sink->idx, sink->port->info.name.ptr));
}
} else {
sink->last_err = status;
sink->last_err_cnt = 0;
}/**
* 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;
}
}断点调试发现,sdl显示设备的驱动是on_clock_tick定时器。

sink->port = 0x044f70fc {info={name={ptr=0x044f70ec "SDL renderer" slen=12 } signature=1448038479 dir=PJMEDIA_DIR_DECODING (2) ...} ...}
这个src是:
port = 0x040fb7cc {info={name={ptr=0x040fb860 "vstdec040FAE74" slen=14 } signature=1347834708 dir=PJMEDIA_DIR_DECODING (2) ...} ...}
原来解码完的数据是放在stream->dec_frame.buf中的,get_frame方法发现dec_frame.size大于0,则拷贝到frame中,用来显示。
static pj_status_t get_frame(pjmedia_port *port,
pjmedia_frame *frame)
{
pjmedia_vid_stream *stream = (pjmedia_vid_stream*) port->port_data.pdata;
pjmedia_vid_channel *channel = stream->dec;
/* Return no frame is channel is paused */
if (channel->paused) {
frame->type = PJMEDIA_FRAME_TYPE_NONE;
frame->size = 0;
return PJ_SUCCESS;
}
/* Report pending events. Do not publish the event while holding the
* stream lock as that would lead to deadlock. It should be safe to
* operate on fmt_event without the mutex because format change normally
* would only occur once during the start of the media.
*/
if (stream->fmt_event.type != PJMEDIA_EVENT_NONE) {
pjmedia_event_fmt_changed_data *fmt_chg_data;
fmt_chg_data = &stream->fmt_event.data.fmt_changed;
#if 1 //realloc size
pj_int32_t new_size = fmt_chg_data->new_fmt.det.vid.size.h*fmt_chg_data->new_fmt.det.vid.size.w*1.5;
if (stream->dec_max_size < new_size)
{
PJ_LOG(5, (THIS_FILE, "Reallocating vid_stream dec_buffer %u --> %u",
(unsigned)stream->dec_max_size,
(unsigned)new_size));
pj_mutex_lock(stream->jb_mutex);
stream->dec_max_size = new_size;
stream->dec_frame.buf = pj_pool_alloc(stream->own_pool, stream->dec_max_size);
pj_mutex_unlock(stream->jb_mutex);
}
#endif//realloc size
/* Update stream info and decoding channel port info */
if (fmt_chg_data->dir == PJMEDIA_DIR_DECODING) {
pjmedia_format_copy(&stream->info.codec_param->dec_fmt,
&fmt_chg_data->new_fmt);
pjmedia_format_copy(&stream->dec->port.info.fmt,
&fmt_chg_data->new_fmt);
/* Override the framerate to be 1.5x higher in the event
* for the renderer.
*/
fmt_chg_data->new_fmt.det.vid.fps.num *= 3;
fmt_chg_data->new_fmt.det.vid.fps.num /= 2;
} else {
pjmedia_format_copy(&stream->info.codec_param->enc_fmt,
&fmt_chg_data->new_fmt);
pjmedia_format_copy(&stream->enc->port.info.fmt,
&fmt_chg_data->new_fmt);
}
dump_port_info(fmt_chg_data->dir==PJMEDIA_DIR_DECODING ?
stream->dec : stream->enc,
"changed");
pjmedia_event_publish(NULL, port, &stream->fmt_event,
PJMEDIA_EVENT_PUBLISH_POST_EVENT);
stream->fmt_event.type = PJMEDIA_EVENT_NONE;
}
if (stream->miss_keyframe_event.type != PJMEDIA_EVENT_NONE) {
pjmedia_event_publish(NULL, port, &stream->miss_keyframe_event,
PJMEDIA_EVENT_PUBLISH_POST_EVENT);
stream->miss_keyframe_event.type = PJMEDIA_EVENT_NONE;
}
pj_grp_lock_acquire( stream->grp_lock );
if (stream->dec_frame.size == 0) {
/* Don't have frame in buffer, try to decode one */
if (decode_frame(stream, frame) != PJ_SUCCESS) {
frame->type = PJMEDIA_FRAME_TYPE_NONE;
frame->size = 0;
}
} else {
if (frame->size < stream->dec_frame.size) {
PJ_LOG(4,(stream->dec->port.info.name.ptr,
"Error: not enough buffer for decoded frame "
"(supplied=%d, required=%d)",
(int)frame->size, (int)stream->dec_frame.size));
frame->type = PJMEDIA_FRAME_TYPE_NONE;
frame->size = 0;
} else {
frame->type = stream->dec_frame.type;
frame->timestamp = stream->dec_frame.timestamp;
frame->size = stream->dec_frame.size;
pj_memcpy(frame->buf, stream->dec_frame.buf, frame->size);
}
stream->dec_frame.size = 0;
}
pj_grp_lock_release( stream->grp_lock );
return PJ_SUCCESS;
}显示stream的port如何与解码输出的port关联的呢?

pjmedia_vid_port_create方法中,有如下代码:
vp_param.active = PJ_FALSE;
pjmedia_vid_port_create中:
vp->role = prm->active ? ROLE_ACTIVE : ROLE_PASSIVE;
PJ_DEF(pj_status_t) pjmedia_vid_port_create( pj_pool_t *pool,
const pjmedia_vid_port_param *prm,
pjmedia_vid_port **p_vid_port)
{
///......
} else if (vp->role==ROLE_PASSIVE) {
vid_pasv_port *pp;
/* Always need to create media port for passive role */
vp->pasv_port = pp = PJ_POOL_ZALLOC_T(pool, vid_pasv_port);
pp->vp = vp;
if (prm->vidparam.dir & PJMEDIA_DIR_CAPTURE)
pp->base.get_frame = &vid_pasv_port_get_frame;
if (prm->vidparam.dir & PJMEDIA_DIR_RENDER)
pp->base.put_frame = &vid_pasv_port_put_frame;
pp->base.on_destroy = &vid_pasv_port_on_destroy;
pjmedia_port_info_init2(&pp->base.info, &vp->dev_name,
PJMEDIA_SIG_VID_PORT,
prm->vidparam.dir, &prm->vidparam.fmt);
need_frame_buf = PJ_TRUE;
}
//pjsua_vid.c
static pj_status_t create_vid_win(pjsua_vid_win_type type,
const pjmedia_format *fmt,
pjmedia_vid_dev_index rend_id,
pjmedia_vid_dev_index cap_id,
pj_bool_t show,
unsigned wnd_flags,
const pjmedia_vid_dev_hwnd *wnd,
pjsua_vid_win_id *id)
/* Create renderer video port, only if it's not a native preview */
if (!w->is_native) {
status = pjmedia_vid_dev_default_param(w->pool, rend_id,
&vp_param.vidparam);
if (status != PJ_SUCCESS)
goto on_error;
vp_param.active = PJ_FALSE;
vp_param.vidparam.dir = PJMEDIA_DIR_RENDER;
vp_param.vidparam.fmt = *fmt;
vp_param.vidparam.disp_size = fmt->det.vid.size;
vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE;
vp_param.vidparam.window_hide = !show;
vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
vp_param.vidparam.window_flags = wnd_flags;
if (wnd) {
vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
vp_param.vidparam.window = *wnd;
}
status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_rend);
if (status != PJ_SUCCESS)
goto on_error;
/* Register renderer to the video conf */
status = pjsua_vid_conf_add_port(
w->pool,
pjmedia_vid_port_get_passive_port(w->vp_rend),
NULL, &w->rend_slot);
if (status != PJ_SUCCESS)
goto on_error;
/* For preview window, connect capturer & renderer (via conf) */
if (w->type == PJSUA_WND_TYPE_PREVIEW && show) {
status = pjsua_vid_conf_connect(w->cap_slot, w->rend_slot, NULL);
if (status != PJ_SUCCESS)
goto on_error;
}
PJ_LOG(4,(THIS_FILE,
"%s window id %d created for cap_dev=%d rend_dev=%d",
pjsua_vid_win_type_name(type), wid, cap_id, rend_id));
} else {
PJ_LOG(4,(THIS_FILE,
"Preview window id %d created for cap_dev %d, "
"using built-in preview!",
wid, cap_id));
}
}
static pj_status_t vid_pasv_port_put_frame(struct pjmedia_port *this_port,
pjmedia_frame *frame)
{
struct vid_pasv_port *vpp = (struct vid_pasv_port*)this_port;
pjmedia_vid_port *vp = vpp->vp;
if (vp->pasv_port->is_destroying)
return PJ_EGONE;
handle_format_change(vp);
if (vp->stream_role==ROLE_PASSIVE) {
/* We are passive and the stream is passive.
* The encoding counterpart is in vid_pasv_port_get_frame().
*/
pj_status_t status;
pjmedia_frame frame_;
if (frame->size != vp->src_size) {
if (frame->size > 0) {
PJ_LOG(4,(THIS_FILE, "Unexpected frame size %lu, expected %lu",
(unsigned long)frame->size,
(unsigned long)vp->src_size));
}
pj_memcpy(&frame_, frame, sizeof(pjmedia_frame));
frame_.buf = NULL;
frame_.size = 0;
/* Send heart beat for updating timestamp or keep-alive. */
return pjmedia_vid_dev_stream_put_frame(vp->strm, &frame_);
}
pj_bzero(&frame_, sizeof(frame_));
status = convert_frame(vp, frame, &frame_);
if (status != PJ_SUCCESS)
return status;
//这里就输出到显示设备的put_frame了
return pjmedia_vid_dev_stream_put_frame(vp->strm, (vp->conv.conv?
&frame_: frame));
} else {
/* We are passive while the stream is active so we just store the
* frame in the buffer.
* The encoding counterpart is located in vidstream_cap_cb()
*/
if (frame->size == vp->src_size)
copy_frame_to_buffer(vp, frame);
}
return PJ_SUCCESS;
}基本上对端视频的解码然后显示的流程就梳理清楚了,要实现一个显示对端摄像头视频的功能就有了基本的思路了。
1、参考sdl_dev.c 实现一个显示的dev,然后注册到factory。
2、解码显示适配。
---------补充分析---
显示窗口:
pjmedia_vid_port*vp_cap;/**< Capture vidport.*/
pjmedia_vid_port *vp_rend; /**< Renderer vidport */
typedef struct pjsua_vid_win
{
pjsua_vid_win_type type; /**< Type. */
pj_pool_t *pool; /**< Own pool. */
unsigned ref_cnt; /**< Reference counter. */
pjmedia_vid_port *vp_cap; /**< Capture vidport. */
pjmedia_vid_port *vp_rend; /**< Renderer vidport */
pjsua_conf_port_id cap_slot; /**< Capturer conf slot */
pjsua_conf_port_id rend_slot; /**< Renderer conf slot */
pjmedia_vid_dev_index preview_cap_id;/**< Capture dev id */
pj_bool_t preview_running;/**< Preview is started*/
pj_bool_t is_native; /**< Preview is by dev */
} pjsua_vid_win;
static pj_status_t create_vid_win(pjsua_vid_win_type type,
const pjmedia_format *fmt,
pjmedia_vid_dev_index rend_id,
pjmedia_vid_dev_index cap_id,
pj_bool_t show,
unsigned wnd_flags,
const pjmedia_vid_dev_hwnd *wnd,
pjsua_vid_win_id *id)
{
if (w->is_native) {
strm = pjmedia_vid_port_get_stream(w->vp_cap);
} else {
strm = pjmedia_vid_port_get_stream(w->vp_rend);
}
pj_assert(strm);
}注册:
选择哪个dev根据什么来决定呢,注册先后顺序。
pj_strdup2_with_null(pool, &vp->dev_name, di.name);
vp->stream_role = di.has_callback ? ROLE_ACTIVE : ROLE_PASSIVE;
/* API: Open video stream object using the specified parameters. */
PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_create(
pjmedia_vid_dev_param *prm,
const pjmedia_vid_dev_cb *cb,
void *user_data,
pjmedia_vid_dev_stream **p_vid_strm)
{
/* Normalize rend_id */
if (prm->dir & PJMEDIA_DIR_RENDER) {
unsigned index;
if (prm->rend_id < 0)
prm->rend_id = PJMEDIA_VID_DEFAULT_RENDER_DEV;
status = lookup_dev(prm->rend_id, &rend_f, &index);
if (status != PJ_SUCCESS)
return status;
prm->rend_id = index;
f = rend_f;
}
/* Internal: lookup device id */
static pj_status_t lookup_dev(pjmedia_vid_dev_index id,
pjmedia_vid_dev_factory **p_f,
unsigned *p_local_index)
{
int f_id, index;
if (id < 0) {
unsigned i;
if (id <= PJMEDIA_VID_INVALID_DEV)
return PJMEDIA_EVID_INVDEV;
for (i=0; i<vid_subsys.drv_cnt; ++i) {
pjmedia_vid_driver *drv = &vid_subsys.drv[i];
if (id==PJMEDIA_VID_DEFAULT_CAPTURE_DEV &&
drv->cap_dev_idx >= 0)
{
id = drv->cap_dev_idx;
make_global_index(i, &id);
break;
} else if (id==PJMEDIA_VID_DEFAULT_RENDER_DEV &&
drv->rend_dev_idx >= 0)
{
id = drv->rend_dev_idx;
make_global_index(i, &id);
break;
}
}
if (id < 0) {
return PJMEDIA_EVID_NODEFDEV;
}
}
f_id = GET_FID(vid_subsys.dev_list[id]);
index = GET_INDEX(vid_subsys.dev_list[id]);
if (f_id < 0 || f_id >= (int)vid_subsys.drv_cnt)
return PJMEDIA_EVID_INVDEV;
if (index < 0 || index >= (int)vid_subsys.drv[f_id].dev_cnt)
return PJMEDIA_EVID_INVDEV;
*p_f = vid_subsys.drv[f_id].f;
*p_local_index = (unsigned)index;
return PJ_SUCCESS;
}lvgl_dev.c 参考
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjmedia-videodev/videodev_imp.h>
#include <pjmedia/event.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/os.h>
#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \
defined(PJMEDIA_VIDEO_DEV_HAS_LVGL) && PJMEDIA_VIDEO_DEV_HAS_LVGL != 0
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
# include "TargetConditionals.h"
# include <Foundation/Foundation.h>
#endif
#define THIS_FILE "lvlg_dev.c"
#define DEFAULT_CLOCK_RATE 90000
#define DEFAULT_WIDTH 640
#define DEFAULT_HEIGHT 480
#define DEFAULT_FPS 25
typedef struct lvgl_fmt_info
{
pjmedia_format_id fmt_id;
pj_uint32_t sdl_format;
pj_uint32_t Rmask;
pj_uint32_t Gmask;
pj_uint32_t Bmask;
pj_uint32_t Amask;
} lvgl_fmt_info;
typedef struct LVGL_Rect
{
int x, y;
int w, h;
} LVGL_Rect;
static lvgl_fmt_info lvgl_fmts[] =
{
#if PJ_IS_BIG_ENDIAN
{PJMEDIA_FORMAT_RGBA, (pj_uint32_t)SDL_PIXELFORMAT_RGBA8888,
0xFF000000, 0xFF0000, 0xFF00, 0xFF} ,
{PJMEDIA_FORMAT_RGB24, (pj_uint32_t)SDL_PIXELFORMAT_RGB24,
0xFF0000, 0xFF00, 0xFF, 0} ,
{PJMEDIA_FORMAT_BGRA, (pj_uint32_t)SDL_PIXELFORMAT_BGRA8888,
0xFF00, 0xFF0000, 0xFF000000, 0xFF} ,
#else /* PJ_IS_BIG_ENDIAN */
{PJMEDIA_FORMAT_RGBA, (pj_uint32_t)0,
0xFF, 0xFF00, 0xFF0000, 0xFF000000} ,
{PJMEDIA_FORMAT_RGB24, (pj_uint32_t)1,
0xFF, 0xFF00, 0xFF0000, 0} ,
{PJMEDIA_FORMAT_BGRA, (pj_uint32_t)2,
0xFF0000, 0xFF00, 0xFF, 0xFF000000} ,
#endif /* PJ_IS_BIG_ENDIAN */
{PJMEDIA_FORMAT_DIB , (pj_uint32_t)3,
0xFF0000, 0xFF00, 0xFF, 0} ,
{PJMEDIA_FORMAT_YUY2, 4, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_UYVY, 5, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_YVYU, 6, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_I420, 7, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_YV12, 8, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_I420JPEG, 9, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_I422JPEG, 10, 0, 0, 0, 0}
};
typedef enum
{
LVGL_WINDOW_FULLSCREEN = 0x00000001, /**< fullscreen window */
LVGL_WINDOW_OPENGL = 0x00000002, /**< window usable with OpenGL context */
LVGL_WINDOW_SHOWN = 0x00000004, /**< window is visible */
LVGL_WINDOW_HIDDEN = 0x00000008, /**< window is not visible */
LVGL_WINDOW_BORDERLESS = 0x00000010, /**< no window decoration */
LVGL_WINDOW_RESIZABLE = 0x00000020, /**< window can be resized */
LVGL_WINDOW_MINIMIZED = 0x00000040, /**< window is minimized */
LVGL_WINDOW_MAXIMIZED = 0x00000080, /**< window is maximized */
LVGL_WINDOW_MOUSE_GRABBED = 0x00000100, /**< window has grabbed mouse input */
LVGL_WINDOW_INPUT_FOCUS = 0x00000200, /**< window has input focus */
LVGL_WINDOW_MOUSE_FOCUS = 0x00000400, /**< window has mouse focus */
LVGL_WINDOW_FULLSCREEN_DESKTOP = (LVGL_WINDOW_FULLSCREEN | 0x00001000),
LVGL_WINDOW_FOREIGN = 0x00000800, /**< window not created by SDL */
LVGL_WINDOW_ALLOW_HIGHDPI = 0x00002000, /**< window should be created in high-DPI mode if supported.
On macOS NSHighResolutionCapable must be set true in the
application's Info.plist for this to have any effect. */
LVGL_WINDOW_MOUSE_CAPTURE = 0x00004000, /**< window has mouse captured (unrelated to MOUSE_GRABBED) */
LVGL_WINDOW_ALWAYS_ON_TOP = 0x00008000, /**< window should always be above others */
LVGL_WINDOW_SKIP_TASKBAR = 0x00010000, /**< window should not be added to the taskbar */
LVGL_WINDOW_UTILITY = 0x00020000, /**< window should be treated as a utility window */
LVGL_WINDOW_TOOLTIP = 0x00040000, /**< window should be treated as a tooltip */
LVGL_WINDOW_POPUP_MENU = 0x00080000, /**< window should be treated as a popup menu */
LVGL_WINDOW_KEYBOARD_GRABBED = 0x00100000, /**< window has grabbed keyboard input */
LVGL_WINDOW_VULKAN = 0x10000000, /**< window usable for Vulkan surface */
LVGL_WINDOW_METAL = 0x20000000, /**< window usable for Metal view */
LVGL_WINDOW_INPUT_GRABBED = LVGL_WINDOW_MOUSE_GRABBED /**< equivalent to LVGL_WINDOW_MOUSE_GRABBED for compatibility */
} SDL_WindowFlags;
/* sdl_ device info */
struct lvgl_dev_info
{
pjmedia_vid_dev_info info;
};
/* Linked list of streams */
struct stream_list
{
PJ_DECL_LIST_MEMBER(struct stream_list);
struct lvgl_stream *stream;
};
#define INITIAL_MAX_JOBS 64
#define JOB_QUEUE_INC_FACTOR 2
typedef pj_status_t (*job_func_ptr)(void *data);
typedef struct job {
job_func_ptr func;
void *data;
unsigned flags;
pj_status_t retval;
} job;
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
@interface JQDelegate: NSObject
{
@public
job *pjob;
}
- (void)run_job;
@end
@implementation JQDelegate
- (void)run_job
{
pjob->retval = (*pjob->func)(pjob->data);
}
@end
#endif /* PJ_DARWINOS */
typedef struct job_queue {
pj_pool_t *pool;
job **jobs;
pj_sem_t **job_sem;
pj_sem_t **old_sem;
pj_mutex_t *mutex;
pj_thread_t *thread;
pj_sem_t *sem;
unsigned size;
unsigned head, tail;
pj_bool_t is_full;
pj_bool_t is_quitting;
} job_queue;
/* lvgl_factory */
struct lvgl_factory
{
pjmedia_vid_dev_factory base;
pj_pool_t *pool;
pj_pool_factory *pf;
unsigned dev_count;
struct lvgl_dev_info *dev_info;
job_queue *jq;
pj_thread_t *lvgl_thread; /**< SDL thread. */
pj_sem_t *sem;
pj_mutex_t *mutex;
struct stream_list streams;
pj_bool_t is_quitting;
pj_thread_desc thread_desc;
pj_thread_t *ev_thread;
};
/* Video stream. */
struct lvgl_stream
{
pjmedia_vid_dev_stream base; /**< Base stream */
pjmedia_vid_dev_param param; /**< Settings */
pj_pool_t *pool; /**< Memory pool. */
pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */
void *user_data; /**< Application data. */
unsigned stream_id;
struct lvgl_factory *sf;
const pjmedia_frame *frame;
pj_bool_t is_running;
pj_timestamp last_ts;
struct stream_list list_entry;
void* window;//实际显示视频的窗口对象
int pitch; /**< Pitch value. */
LVGL_Rect rect; /**< Frame rectangle. */
LVGL_Rect dstrect; /**< Display rectangle. */
pjmedia_video_apply_fmt_param vafp;
};
/* Prototypes */
static pj_status_t lvgl_factory_init(pjmedia_vid_dev_factory *f);
static pj_status_t lvgl_factory_destroy(pjmedia_vid_dev_factory *f);
static pj_status_t lvgl_factory_refresh(pjmedia_vid_dev_factory *f);
static unsigned lvgl_factory_get_dev_count(pjmedia_vid_dev_factory *f);
static pj_status_t lvgl_factory_get_dev_info(pjmedia_vid_dev_factory *f,
unsigned index,
pjmedia_vid_dev_info *info);
static pj_status_t lvgl_factory_default_param(pj_pool_t *pool,
pjmedia_vid_dev_factory *f,
unsigned index,
pjmedia_vid_dev_param *param);
static pj_status_t lvgl_factory_create_stream(
pjmedia_vid_dev_factory *f,
pjmedia_vid_dev_param *param,
const pjmedia_vid_dev_cb *cb,
void *user_data,
pjmedia_vid_dev_stream **p_vid_strm);
static pj_status_t lvgl_stream_get_param(pjmedia_vid_dev_stream *strm,
pjmedia_vid_dev_param *param);
static pj_status_t lvgl_stream_get_cap(pjmedia_vid_dev_stream *strm,
pjmedia_vid_dev_cap cap,
void *value);
static pj_status_t lvgl_stream_set_cap(pjmedia_vid_dev_stream *strm,
pjmedia_vid_dev_cap cap,
const void *value);
static pj_status_t lvgl_stream_put_frame(pjmedia_vid_dev_stream *strm,
const pjmedia_frame *frame);
static pj_status_t lvgl_stream_start(pjmedia_vid_dev_stream *strm);
static pj_status_t lvgl_stream_stop(pjmedia_vid_dev_stream *strm);
static pj_status_t lvgl_stream_destroy(pjmedia_vid_dev_stream *strm);
static pj_status_t resize_disp(struct lvgl_stream *strm,
pjmedia_rect_size *new_disp_size);
static pj_status_t lvgl_destroy_all(void *data);
/* Job queue prototypes */
static pj_status_t job_queue_create(pj_pool_t *pool, job_queue **pjq);
static pj_status_t job_queue_post_job(job_queue *jq, job_func_ptr func,
void *data, unsigned flags,
pj_status_t *retval);
static pj_status_t job_queue_destroy(job_queue *jq);
/* Operations */
static pjmedia_vid_dev_factory_op factory_op =
{
&lvgl_factory_init,
&lvgl_factory_destroy,
&lvgl_factory_get_dev_count,
&lvgl_factory_get_dev_info,
&lvgl_factory_default_param,
&lvgl_factory_create_stream,
&lvgl_factory_refresh
};
static pjmedia_vid_dev_stream_op stream_op =
{
&lvgl_stream_get_param,
&lvgl_stream_get_cap,
&lvgl_stream_set_cap,
&lvgl_stream_start,
NULL,
&lvgl_stream_put_frame,
&lvgl_stream_stop,
&lvgl_stream_destroy
};
/*
* Util
*/
static void sdl_log_err(const char *op)
{
PJ_LOG(1,(THIS_FILE, "%s error: %s", op, SDL_GetError()));
}
/****************************************************************************
* Factory operations
*/
/*
* Init sdl_ video driver.
*/
pjmedia_vid_dev_factory* pjmedia_lvgl_factory(pj_pool_factory *pf)
{
struct lvgl_factory *f;
pj_pool_t *pool;
pool = pj_pool_create(pf, "lvgl_dev video", 4000, 4000, NULL);
f = PJ_POOL_ZALLOC_T(pool, struct lvgl_factory);
f->pf = pf;
f->pool = pool;
f->base.op = &factory_op;
return &f->base;
}
static pj_status_t lvgl_init(void * data)
{
PJ_UNUSED_ARG(data);
return PJ_SUCCESS;
}
static struct lvgl_stream* find_stream(struct lvgl_factory *sf,
pj_uint32_t windowID,
pjmedia_event *pevent)
{
struct stream_list *it, *itBegin;
struct lvgl_stream *strm = NULL;
itBegin = &sf->streams;
for (it = itBegin->next; it != itBegin; it = it->next) {
if (it->stream->stream_id == windowID)
{
strm = it->stream;
break;
}
}
if (strm)
pjmedia_event_init(pevent, PJMEDIA_EVENT_NONE, &strm->last_ts,
strm);
return strm;
}
static pj_status_t handle_event(void *data)
{
struct lvgl_factory *sf = (struct lvgl_factory*)data;
if (!pj_thread_is_registered())
pj_thread_register("sdl_ev", sf->thread_desc, &sf->ev_thread);
return PJ_SUCCESS;
}
static int lvgl_ev_thread(void *data)
{
struct lvgl_factory *sf = (struct lvgl_factory*)data;
while(1) {
pj_status_t status;
pj_mutex_lock(sf->mutex);
if (pj_list_empty(&sf->streams)) {
pj_mutex_unlock(sf->mutex);
/* Wait until there is any stream. */
pj_sem_wait(sf->sem);
} else
pj_mutex_unlock(sf->mutex);
if (sf->is_quitting)
break;
job_queue_post_job(sf->jq, handle_event, sf, 0, &status);
pj_thread_sleep(50);
}
return 0;
}
static pj_status_t lvgl_quit(void *data)
{
PJ_UNUSED_ARG(data);
return PJ_SUCCESS;
}
/* API: init factory */
static pj_status_t lvgl_factory_init(pjmedia_vid_dev_factory *f)
{
struct lvgl_factory *sf = (struct lvgl_factory*)f;
struct lvgl_dev_info *ddi;
unsigned i, j;
pj_status_t status;
pj_list_init(&sf->streams);
status = job_queue_create(sf->pool, &sf->jq);
if (status != PJ_SUCCESS)
return PJMEDIA_EVID_INIT;
job_queue_post_job(sf->jq, lvgl_init, NULL, 0, &status);
if (status != PJ_SUCCESS)
return status;
status = pj_mutex_create_recursive(sf->pool, "lvgl_factory",
&sf->mutex);
if (status != PJ_SUCCESS)
return status;
status = pj_sem_create(sf->pool, NULL, 0, 1, &sf->sem);
if (status != PJ_SUCCESS)
return status;
#if 0
/* Create event handler thread. */
status = pj_thread_create(sf->pool, "lvgl_thread", lvgl_ev_thread,
sf, 0, 0, &sf->lvgl_thread);
if (status != PJ_SUCCESS)
return status;
#endif//
sf->dev_count = 1;
sf->dev_info = (struct lvgl_dev_info*)
pj_pool_calloc(sf->pool, sf->dev_count,
sizeof(struct lvgl_dev_info));
ddi = &sf->dev_info[0];
pj_bzero(ddi, sizeof(*ddi));
pj_ansi_strxcpy(ddi->info.name, "lvgl_dev renderer",
sizeof(ddi->info.name));
ddi->info.fmt_cnt = PJ_ARRAY_SIZE(lvgl_fmts);
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
ddi = &sf->dev_info[OPENGL_DEV_IDX];
pj_bzero(ddi, sizeof(*ddi));
pj_ansi_strxcpy(ddi->info.name, "LVGL openGL renderer",
sizeof(ddi->info.name));
ddi->info.fmt_cnt = 1;
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
for (i = 0; i < sf->dev_count; i++) {
ddi = &sf->dev_info[i];
pj_ansi_strxcpy(ddi->info.driver, "LVGL-dev",
sizeof(ddi->info.driver));
ddi->info.dir = PJMEDIA_DIR_RENDER;
ddi->info.has_callback = PJ_FALSE;
ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT |
PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE;
ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
for (j = 0; j < ddi->info.fmt_cnt; j++) {
pjmedia_format *fmt = &ddi->info.fmt[j];
pjmedia_format_init_video(fmt, lvgl_fmts[j].fmt_id,
DEFAULT_WIDTH, DEFAULT_HEIGHT,
DEFAULT_FPS, 1);
}
}
PJ_LOG(4, (THIS_FILE, "LVGL %d.%d initialized",
0, 1));
return PJ_SUCCESS;
}
/* API: destroy factory */
static pj_status_t lvgl_factory_destroy(pjmedia_vid_dev_factory *f)
{
struct lvgl_factory *sf = (struct lvgl_factory*)f;
pj_pool_t *pool = sf->pool;
pj_status_t status;
pj_assert(pj_list_empty(&sf->streams));
sf->is_quitting = PJ_TRUE;
if (sf->lvgl_thread) {
pj_sem_post(sf->sem);
pj_thread_join(sf->lvgl_thread);
pj_thread_destroy(sf->lvgl_thread);
}
if (sf->mutex) {
pj_mutex_destroy(sf->mutex);
sf->mutex = NULL;
}
if (sf->sem) {
pj_sem_destroy(sf->sem);
sf->sem = NULL;
}
job_queue_post_job(sf->jq, lvgl_quit, NULL, 0, &status);
job_queue_destroy(sf->jq);
sf->pool = NULL;
pj_pool_release(pool);
return PJ_SUCCESS;
}
/* API: refresh the list of devices */
static pj_status_t lvgl_factory_refresh(pjmedia_vid_dev_factory *f)
{
PJ_UNUSED_ARG(f);
return PJ_SUCCESS;
}
/* API: get number of devices */
static unsigned lvgl_factory_get_dev_count(pjmedia_vid_dev_factory *f)
{
struct lvgl_factory *sf = (struct lvgl_factory*)f;
return sf->dev_count;
}
/* API: get device info */
static pj_status_t lvgl_factory_get_dev_info(pjmedia_vid_dev_factory *f,
unsigned index,
pjmedia_vid_dev_info *info)
{
struct lvgl_factory *sf = (struct lvgl_factory*)f;
PJ_ASSERT_RETURN(index < sf->dev_count, PJMEDIA_EVID_INVDEV);
pj_memcpy(info, &sf->dev_info[index].info, sizeof(*info));
return PJ_SUCCESS;
}
/* API: create default device parameter */
static pj_status_t lvgl_factory_default_param(pj_pool_t *pool,
pjmedia_vid_dev_factory *f,
unsigned index,
pjmedia_vid_dev_param *param)
{
struct lvgl_factory *sf = (struct lvgl_factory*)f;
struct lvgl_dev_info *di = &sf->dev_info[index];
PJ_ASSERT_RETURN(index < sf->dev_count, PJMEDIA_EVID_INVDEV);
PJ_UNUSED_ARG(pool);
pj_bzero(param, sizeof(*param));
param->dir = PJMEDIA_DIR_RENDER;
param->rend_id = index;
param->cap_id = PJMEDIA_VID_INVALID_DEV;
/* Set the device capabilities here */
param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
param->fmt.type = PJMEDIA_TYPE_VIDEO;
param->clock_rate = DEFAULT_CLOCK_RATE;
pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt));
return PJ_SUCCESS;
}
static lvgl_fmt_info* get_sdl_format_info(pjmedia_format_id id)
{
unsigned i;
for (i = 0; i < PJ_ARRAY_SIZE(lvgl_fmts); i++) {
if (lvgl_fmts[i].fmt_id == id)
return &lvgl_fmts[i];
}
return NULL;
}
static pj_status_t lvgl_destroy(void *data)
{
struct lvgl_stream *strm = (struct lvgl_stream *)data;
strm->window = NULL;
return PJ_SUCCESS;
}
static pj_status_t lvgl_destroy_all(void *data)
{
struct lvgl_stream *strm = (struct lvgl_stream *)data;
lvgl_destroy(data);
return PJ_SUCCESS;
}
static pj_status_t lvgl_create_window(struct lvgl_stream *strm,
pj_bool_t use_app_win,
pj_uint32_t sdl_format,
pjmedia_vid_dev_hwnd *hwnd)
{
//((pjmedia_coord *)pval)->x, &((pjmedia_coord*)pval)->y
if (!strm->window) {
}
return PJ_SUCCESS;
}
static pj_status_t lvgl_create_rend(struct lvgl_stream * strm,
pjmedia_format *fmt)
{
lvgl_fmt_info *sdl_info;
const pjmedia_video_format_info *vfi;
pjmedia_video_format_detail *vfd;
sdl_info = get_sdl_format_info(fmt->id);
vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(),
fmt->id);
if (!vfi || !sdl_info)
return PJMEDIA_EVID_BADFORMAT;
strm->vafp.size = fmt->det.vid.size;
strm->vafp.buffer = NULL;
if (vfi->apply_fmt(vfi, &strm->vafp) != PJ_SUCCESS)
return PJMEDIA_EVID_BADFORMAT;
vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE);
strm->rect.x = strm->rect.y = 0;
//strm->rect.w = (Uint16)vfd->size.w;
//strm->rect.h = (Uint16)vfd->size.h;
if (strm->param.disp_size.w == 0)
strm->param.disp_size.w = strm->rect.w;
if (strm->param.disp_size.h == 0)
strm->param.disp_size.h = strm->rect.h;
strm->dstrect.x = strm->dstrect.y = 0;
//strm->dstrect.w = (Uint16)strm->param.disp_size.w;
//strm->dstrect.h = (Uint16)strm->param.disp_size.h;
lvgl_destroy(strm);
return lvgl_create_window(strm,
(strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW),
sdl_info->sdl_format,
&strm->param.window);
}
static pj_status_t lvgl_create(void *data)
{
struct lvgl_stream *strm = (struct lvgl_stream *)data;
return lvgl_create_rend(strm, &strm->param.fmt);
}
static pj_status_t resize_disp(struct lvgl_stream *strm,
pjmedia_rect_size *new_disp_size)
{
pj_memcpy(&strm->param.disp_size, new_disp_size,
sizeof(strm->param.disp_size));
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
else if (strm->param.rend_id == OPENGL_DEV_IDX) {
lvgl_create_rend(strm, &strm->param.fmt);
}
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
return PJ_SUCCESS;
}
static pj_status_t change_format(struct lvgl_stream *strm,
pjmedia_format *new_fmt)
{
pj_status_t status;
/* Recreate SDL renderer */
status = lvgl_create_rend(strm, (new_fmt? new_fmt :
&strm->param.fmt));
if (status == PJ_SUCCESS && new_fmt)
pjmedia_format_copy(&strm->param.fmt, new_fmt);
return status;
}
static pj_status_t put_frame(void *data)
{
struct lvgl_stream *stream = (struct lvgl_stream *)data;
const pjmedia_frame *frame = stream->frame;
//实际的视频绘制
//todo
return PJ_SUCCESS;
}
/* API: Put frame from stream */
static pj_status_t lvgl_stream_put_frame(pjmedia_vid_dev_stream *strm,
const pjmedia_frame *frame)
{
struct lvgl_stream *stream = (struct lvgl_stream*)strm;
pj_status_t status;
stream->last_ts.u64 = frame->timestamp.u64;
/* Video conference just trying to send heart beat for updating timestamp
* or keep-alive, this port doesn't need any, just ignore.
*/
if (frame->size==0 || frame->buf==NULL)
return PJ_SUCCESS;
if (frame->size < stream->vafp.framebytes)
return PJ_ETOOSMALL;
if (!stream->is_running)
return PJ_EINVALIDOP;
stream->frame = frame;
job_queue_post_job(stream->sf->jq, put_frame, strm, 0, &status);
return status;
}
/* API: create stream */
static pj_status_t lvgl_factory_create_stream(
pjmedia_vid_dev_factory *f,
pjmedia_vid_dev_param *param,
const pjmedia_vid_dev_cb *cb,
void *user_data,
pjmedia_vid_dev_stream **p_vid_strm)
{
struct lvgl_factory *sf = (struct lvgl_factory*)f;
pj_pool_t *pool;
struct lvgl_stream *strm;
pj_status_t status;
PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL);
/* Create and Initialize stream descriptor */
pool = pj_pool_create(sf->pf, "sdl-dev", 1000, 1000, NULL);
PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
strm = PJ_POOL_ZALLOC_T(pool, struct lvgl_stream);
pj_memcpy(&strm->param, param, sizeof(*param));
strm->pool = pool;
strm->sf = sf;
pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
pj_list_init(&strm->list_entry);
strm->list_entry.stream = strm;
strm->user_data = user_data;
/* Create render stream here */
job_queue_post_job(sf->jq, lvgl_create, strm, 0, &status);
if (status != PJ_SUCCESS) {
goto on_error;
}
pj_mutex_lock(strm->sf->mutex);
if (pj_list_empty(&strm->sf->streams))
pj_sem_post(strm->sf->sem);
pj_list_insert_after(&strm->sf->streams, &strm->list_entry);
pj_mutex_unlock(strm->sf->mutex);
/* Done */
strm->base.op = &stream_op;
*p_vid_strm = &strm->base;
return PJ_SUCCESS;
on_error:
lvgl_stream_destroy(&strm->base);
return status;
}
/* API: Get stream info. */
static pj_status_t lvgl_stream_get_param(pjmedia_vid_dev_stream *s,
pjmedia_vid_dev_param *pi)
{
struct lvgl_stream *strm = (struct lvgl_stream*)s;
PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
pj_memcpy(pi, &strm->param, sizeof(*pi));
if (lvgl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
&pi->window) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
}
if (lvgl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION,
&pi->window_pos) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION;
}
if (lvgl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE,
&pi->disp_size) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE;
}
if (lvgl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE,
&pi->window_hide) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE;
}
if (lvgl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS,
&pi->window_flags) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
}
if (lvgl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_FULLSCREEN,
&pi->window_fullscreen) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_FULLSCREEN;
}
return PJ_SUCCESS;
}
struct strm_cap {
struct lvgl_stream *strm;
pjmedia_vid_dev_cap cap;
union {
void *pval;
const void *cpval;
} pval;
};
static pj_status_t get_cap(void *data)
{
struct strm_cap *scap = (struct strm_cap *)data;
struct lvgl_stream *strm = scap->strm;
pjmedia_vid_dev_cap cap = scap->cap;
void *pval = scap->pval.pval;
if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)
{
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) {
//((pjmedia_coord *)pval)->x, &((pjmedia_coord*)pval)->y
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE) {
//((pjmedia_coord *)pval)->x, &((pjmedia_coord*)pval)->y
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) {
//pj_uint32_t flag = SDL_GetWindowFlags(strm->window);
//*((pj_bool_t *)pval) = (flag & LVGL_WINDOW_HIDDEN)? PJ_TRUE: PJ_FALSE;
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS) {
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_FULLSCREEN) {
return PJ_SUCCESS;
}
return PJMEDIA_EVID_INVCAP;
}
/* API: get capability */
static pj_status_t lvgl_stream_get_cap(pjmedia_vid_dev_stream *s,
pjmedia_vid_dev_cap cap,
void *pval)
{
struct lvgl_stream *strm = (struct lvgl_stream*)s;
struct strm_cap scap;
pj_status_t status;
PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
scap.strm = strm;
scap.cap = cap;
scap.pval.pval = pval;
job_queue_post_job(strm->sf->jq, get_cap, &scap, 0, &status);
return status;
}
static pj_status_t set_cap(void *data)
{
struct strm_cap *scap = (struct strm_cap *)data;
struct lvgl_stream *strm = scap->strm;
pjmedia_vid_dev_cap cap = scap->cap;
const void *pval = scap->pval.cpval;
PJ_ASSERT_RETURN(data && strm, PJ_EINVAL);
if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) {
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) {
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_FORMAT) {
pj_status_t status;
status = change_format(strm, (pjmedia_format *)pval);
if (status != PJ_SUCCESS) {
pj_status_t status_;
/**
* Failed to change the output format. Try to revert
* to its original format.
*/
status_ = change_format(strm, &strm->param.fmt);
if (status_ != PJ_SUCCESS) {
/**
* This means that we failed to revert to our
* original state!
*/
status = PJMEDIA_EVID_ERR;
}
}
return status;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE) {
pjmedia_rect_size *new_size = (pjmedia_rect_size *)pval;
pj_status_t status = 0;
return status;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
pjmedia_vid_dev_hwnd *hwnd = (pjmedia_vid_dev_hwnd*)pval;
pj_status_t status = PJ_SUCCESS;
lvgl_fmt_info *sdl_info = get_sdl_format_info(strm->param.fmt.id);
/* Re-init SDL */
status = lvgl_destroy_all(strm);
if (status != PJ_SUCCESS)
return status;
status = lvgl_create_window(strm, PJ_TRUE, sdl_info->sdl_format, hwnd);
PJ_PERROR(4, (THIS_FILE, status,
"Re-initializing SDL with native window %p",
hwnd->info.window));
return status;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_FULLSCREEN) {
pj_uint32_t flag;
pjmedia_vid_dev_fullscreen_flag val =
*(pjmedia_vid_dev_fullscreen_flag*)pval;
return PJ_SUCCESS;
}
return PJMEDIA_EVID_INVCAP;
}
/* API: set capability */
static pj_status_t lvgl_stream_set_cap(pjmedia_vid_dev_stream *s,
pjmedia_vid_dev_cap cap,
const void *pval)
{
struct lvgl_stream *strm = (struct lvgl_stream*)s;
struct strm_cap scap;
pj_status_t status;
PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
scap.strm = strm;
scap.cap = cap;
scap.pval.cpval = pval;
job_queue_post_job(strm->sf->jq, set_cap, &scap, 0, &status);
return status;
}
/* API: Start stream. */
static pj_status_t lvgl_stream_start(pjmedia_vid_dev_stream *strm)
{
struct lvgl_stream *stream = (struct lvgl_stream*)strm;
PJ_LOG(4, (THIS_FILE, "Starting sdl video stream"));
stream->is_running = PJ_TRUE;
stream->window = NULL;
return PJ_SUCCESS;
}
/* API: Stop stream. */
static pj_status_t lvgl_stream_stop(pjmedia_vid_dev_stream *strm)
{
struct lvgl_stream *stream = (struct lvgl_stream*)strm;
PJ_LOG(4, (THIS_FILE, "Stopping sdl video stream"));
stream->is_running = PJ_FALSE;
return PJ_SUCCESS;
}
/* API: Destroy stream. */
static pj_status_t lvgl_stream_destroy(pjmedia_vid_dev_stream *strm)
{
struct lvgl_stream *stream = (struct lvgl_stream*)strm;
pj_status_t status;
PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
lvgl_stream_stop(strm);
job_queue_post_job(stream->sf->jq, lvgl_destroy_all, strm, 0, &status);
if (status != PJ_SUCCESS)
return status;
pj_mutex_lock(stream->sf->mutex);
if (!pj_list_empty(&stream->list_entry))
pj_list_erase(&stream->list_entry);
pj_mutex_unlock(stream->sf->mutex);
pj_pool_release(stream->pool);
return PJ_SUCCESS;
}
/****************************************************************************
* Job queue implementation
*/
#if PJ_DARWINOS==0
static int lvgl_job_thread(void * data)
{
job_queue *jq = (job_queue *)data;
while (1) {
job *jb;
/* Wait until there is a job. */
pj_sem_wait(jq->sem);
/* Make sure there is no pending jobs before we quit. */
if (jq->is_quitting && jq->head == jq->tail && !jq->is_full)
break;
jb = jq->jobs[jq->head];
jb->retval = (*jb->func)(jb->data);
/* If job queue is full and we already finish all the pending
* jobs, increase the size.
*/
if (jq->is_full && ((jq->head + 1) % jq->size == jq->tail)) {
unsigned i, head;
pj_status_t status;
if (jq->old_sem) {
for (i = 0; i < jq->size / JOB_QUEUE_INC_FACTOR; i++) {
pj_sem_destroy(jq->old_sem[i]);
}
}
jq->old_sem = jq->job_sem;
/* Double the job queue size. */
jq->size *= JOB_QUEUE_INC_FACTOR;
pj_sem_destroy(jq->sem);
status = pj_sem_create(jq->pool, "thread_sem", 0, jq->size + 1,
&jq->sem);
if (status != PJ_SUCCESS) {
PJ_PERROR(3, (THIS_FILE, status,
"Failed growing SDL job queue size."));
return 0;
}
jq->jobs = (job **)pj_pool_calloc(jq->pool, jq->size,
sizeof(job *));
jq->job_sem = (pj_sem_t **) pj_pool_calloc(jq->pool, jq->size,
sizeof(pj_sem_t *));
for (i = 0; i < jq->size; i++) {
status = pj_sem_create(jq->pool, "job_sem", 0, 1,
&jq->job_sem[i]);
if (status != PJ_SUCCESS) {
PJ_PERROR(3, (THIS_FILE, status,
"Failed growing SDL job queue size."));
return 0;
}
}
jq->is_full = PJ_FALSE;
head = jq->head;
jq->head = jq->tail = 0;
pj_sem_post(jq->old_sem[head]);
} else {
pj_sem_post(jq->job_sem[jq->head]);
jq->head = (jq->head + 1) % jq->size;
}
}
return 0;
}
#endif
static pj_status_t job_queue_create(pj_pool_t *pool, job_queue **pjq)
{
unsigned i;
pj_status_t status;
job_queue *jq = PJ_POOL_ZALLOC_T(pool, job_queue);
jq->pool = pool;
jq->size = INITIAL_MAX_JOBS;
status = pj_sem_create(pool, "lvgl_thread_sem", 0, jq->size + 1, &jq->sem);
if (status != PJ_SUCCESS)
goto on_error;
jq->jobs = (job **)pj_pool_calloc(pool, jq->size, sizeof(job *));
jq->job_sem = (pj_sem_t **) pj_pool_calloc(pool, jq->size,
sizeof(pj_sem_t *));
for (i = 0; i < jq->size; i++) {
status = pj_sem_create(pool, "lvgl_job_sem", 0, 1, &jq->job_sem[i]);
if (status != PJ_SUCCESS)
goto on_error;
}
status = pj_mutex_create_recursive(pool, "lvgl_job_mutex", &jq->mutex);
if (status != PJ_SUCCESS)
goto on_error;
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
PJ_UNUSED_ARG(status);
#else
status = pj_thread_create(pool, "lvgl_job_th", lvgl_job_thread, jq, 0, 0,
&jq->thread);
if (status != PJ_SUCCESS)
goto on_error;
#endif /* PJ_DARWINOS */
*pjq = jq;
return PJ_SUCCESS;
on_error:
job_queue_destroy(jq);
return status;
}
static pj_status_t job_queue_post_job(job_queue *jq, job_func_ptr func,
void *data, unsigned flags,
pj_status_t *retval)
{
job jb;
int tail;
if (jq->is_quitting) {
jb.retval = PJ_EBUSY;
goto on_return;
}
jb.func = func;
jb.data = data;
jb.flags = flags;
jb.retval = PJ_SUCCESS;
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
PJ_UNUSED_ARG(tail);
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc]init];
JQDelegate *jqd = [[JQDelegate alloc]init];
jqd->pjob = &jb;
[jqd performSelectorOnMainThread:@selector(run_job)
withObject:nil waitUntilDone:YES];
[jqd release];
[apool release];
#else /* PJ_DARWINOS */
pj_mutex_lock(jq->mutex);
jq->jobs[jq->tail] = &jb;
tail = jq->tail;
jq->tail = (jq->tail + 1) % jq->size;
if (jq->tail == jq->head) {
jq->is_full = PJ_TRUE;
PJ_LOG(4, (THIS_FILE, "lvgl-dev job queue is full, increasing "
"the queue size."));
pj_mutex_unlock(jq->mutex);
pj_sem_post(jq->sem);
/* Wait until our posted job is completed. */
pj_sem_wait(jq->job_sem[tail]);
} else {
pj_mutex_unlock(jq->mutex);
pj_sem_post(jq->sem);
/* Wait until our posted job is completed. */
pj_sem_wait(jq->job_sem[tail]);
}
#endif /* PJ_DARWINOS */
on_return:
if (retval)
*retval = jb.retval;
return jb.retval;
}
static pj_status_t job_queue_destroy(job_queue *jq)
{
unsigned i;
jq->is_quitting = PJ_TRUE;
if (jq->thread) {
pj_sem_post(jq->sem);
pj_thread_join(jq->thread);
pj_thread_destroy(jq->thread);
}
if (jq->sem) {
pj_sem_destroy(jq->sem);
jq->sem = NULL;
}
for (i = 0; i < jq->size; i++) {
if (jq->job_sem[i]) {
pj_sem_destroy(jq->job_sem[i]);
jq->job_sem[i] = NULL;
}
}
if (jq->old_sem) {
for (i = 0; i < jq->size / JOB_QUEUE_INC_FACTOR; i++) {
if (jq->old_sem[i]) {
pj_sem_destroy(jq->old_sem[i]);
jq->old_sem[i] = NULL;
}
}
}
if (jq->mutex) {
pj_mutex_destroy(jq->mutex);
jq->mutex = NULL;
}
return PJ_SUCCESS;
}
#endif /* PJMEDIA_VIDEO_DEV_HAS_LVGL */-------------------广告线---------------
项目、合作,欢迎勾搭,邮箱:promall@qq.com
本文为呱牛笔记原创文章,转载无需和我联系,但请注明来自呱牛笔记 ,it3q.com
