| /* $Id$ */ |
| /* |
| * 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 |
| */ |
| |
| /* Video device with ffmpeg backend, currently only capture devices are |
| * implemented. |
| * |
| * Issues: |
| * - no device enumeration (ffmpeg limitation), so this uses "host API" enum |
| * instead |
| * - need stricter filter on "host API" enum, currently audio capture devs are |
| * still listed. |
| * - no format enumeration, currently hardcoded to PJMEDIA_FORMAT_RGB24 only |
| * - tested on Vista only (vfw backend) with virtual cam |
| * - vfw backend produce bottom up pictures |
| * - using VS IDE, this cannot run under debugger! |
| */ |
| |
| #include <pjmedia-videodev/videodev_imp.h> |
| #include <pj/assert.h> |
| #include <pj/log.h> |
| #include <pj/os.h> |
| #include <pj/unicode.h> |
| |
| |
| #if defined(PJMEDIA_VIDEO_DEV_HAS_FFMPEG) && PJMEDIA_VIDEO_DEV_HAS_FFMPEG != 0 |
| |
| |
| #define THIS_FILE "ffmpeg.c" |
| |
| #include "../pjmedia/ffmpeg_util.h" |
| #include <libavdevice/avdevice.h> |
| #include <libavformat/avformat.h> |
| |
| #define MAX_DEV_CNT 8 |
| |
| typedef struct ffmpeg_dev_info |
| { |
| pjmedia_vid_dev_info base; |
| AVInputFormat *host_api; |
| const char *def_devname; |
| } ffmpeg_dev_info; |
| |
| |
| typedef struct ffmpeg_factory |
| { |
| pjmedia_vid_dev_factory base; |
| pj_pool_factory *pf; |
| pj_pool_t *pool; |
| pj_pool_t *dev_pool; |
| unsigned dev_count; |
| ffmpeg_dev_info dev_info[MAX_DEV_CNT]; |
| } ffmpeg_factory; |
| |
| |
| typedef struct ffmpeg_stream |
| { |
| pjmedia_vid_dev_stream base; |
| ffmpeg_factory *factory; |
| pj_pool_t *pool; |
| pjmedia_vid_dev_param param; |
| AVFormatContext *ff_fmt_ctx; |
| } ffmpeg_stream; |
| |
| |
| /* Prototypes */ |
| static pj_status_t ffmpeg_factory_init(pjmedia_vid_dev_factory *f); |
| static pj_status_t ffmpeg_factory_destroy(pjmedia_vid_dev_factory *f); |
| static pj_status_t ffmpeg_factory_refresh(pjmedia_vid_dev_factory *f); |
| static unsigned ffmpeg_factory_get_dev_count(pjmedia_vid_dev_factory *f); |
| static pj_status_t ffmpeg_factory_get_dev_info(pjmedia_vid_dev_factory *f, |
| unsigned index, |
| pjmedia_vid_dev_info *info); |
| static pj_status_t ffmpeg_factory_default_param(pj_pool_t *pool, |
| pjmedia_vid_dev_factory *f, |
| unsigned index, |
| pjmedia_vid_dev_param *param); |
| static pj_status_t ffmpeg_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 ffmpeg_stream_get_param(pjmedia_vid_dev_stream *strm, |
| pjmedia_vid_dev_param *param); |
| static pj_status_t ffmpeg_stream_get_cap(pjmedia_vid_dev_stream *strm, |
| pjmedia_vid_dev_cap cap, |
| void *value); |
| static pj_status_t ffmpeg_stream_set_cap(pjmedia_vid_dev_stream *strm, |
| pjmedia_vid_dev_cap cap, |
| const void *value); |
| static pj_status_t ffmpeg_stream_start(pjmedia_vid_dev_stream *strm); |
| static pj_status_t ffmpeg_stream_get_frame(pjmedia_vid_dev_stream *s, |
| pjmedia_frame *frame); |
| static pj_status_t ffmpeg_stream_stop(pjmedia_vid_dev_stream *strm); |
| static pj_status_t ffmpeg_stream_destroy(pjmedia_vid_dev_stream *strm); |
| |
| /* Operations */ |
| static pjmedia_vid_dev_factory_op factory_op = |
| { |
| &ffmpeg_factory_init, |
| &ffmpeg_factory_destroy, |
| &ffmpeg_factory_get_dev_count, |
| &ffmpeg_factory_get_dev_info, |
| &ffmpeg_factory_default_param, |
| &ffmpeg_factory_create_stream, |
| &ffmpeg_factory_refresh |
| }; |
| |
| static pjmedia_vid_dev_stream_op stream_op = |
| { |
| &ffmpeg_stream_get_param, |
| &ffmpeg_stream_get_cap, |
| &ffmpeg_stream_set_cap, |
| &ffmpeg_stream_start, |
| &ffmpeg_stream_get_frame, |
| NULL, |
| &ffmpeg_stream_stop, |
| &ffmpeg_stream_destroy |
| }; |
| |
| |
| static void print_ffmpeg_err(int err) |
| { |
| char errbuf[512]; |
| if (av_strerror(err, errbuf, sizeof(errbuf)) >= 0) |
| PJ_LOG(1, (THIS_FILE, "ffmpeg err %d: %s", err, errbuf)); |
| |
| } |
| |
| static void print_ffmpeg_log(void* ptr, int level, const char* fmt, va_list vl) |
| { |
| PJ_UNUSED_ARG(ptr); |
| PJ_UNUSED_ARG(level); |
| vfprintf(stdout, fmt, vl); |
| } |
| |
| |
| static pj_status_t ffmpeg_capture_open(AVFormatContext **ctx, |
| AVInputFormat *ifmt, |
| const char *dev_name, |
| const pjmedia_vid_dev_param *param) |
| { |
| AVFormatParameters fp; |
| pjmedia_video_format_detail *vfd; |
| int err; |
| |
| PJ_ASSERT_RETURN(ctx && ifmt && dev_name && param, PJ_EINVAL); |
| PJ_ASSERT_RETURN(param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO, |
| PJ_EINVAL); |
| |
| vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); |
| |
| /* Init ffmpeg format context */ |
| *ctx = avformat_alloc_context(); |
| |
| /* Init ffmpeg format param */ |
| pj_bzero(&fp, sizeof(fp)); |
| fp.prealloced_context = 1; |
| fp.width = vfd->size.w; |
| fp.height = vfd->size.h; |
| fp.pix_fmt = PIX_FMT_BGR24; |
| fp.time_base.num = vfd->fps.denum; |
| fp.time_base.den = vfd->fps.num; |
| |
| /* Open capture stream */ |
| err = av_open_input_stream(ctx, NULL, dev_name, ifmt, &fp); |
| if (err < 0) { |
| *ctx = NULL; /* ffmpeg freed its states on failure, do we must too */ |
| print_ffmpeg_err(err); |
| return PJ_EUNKNOWN; |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| static void ffmpeg_capture_close(AVFormatContext *ctx) |
| { |
| if (ctx) |
| av_close_input_stream(ctx); |
| } |
| |
| |
| /**************************************************************************** |
| * Factory operations |
| */ |
| /* |
| * Init ffmpeg_ video driver. |
| */ |
| pjmedia_vid_dev_factory* pjmedia_ffmpeg_factory(pj_pool_factory *pf) |
| { |
| ffmpeg_factory *f; |
| pj_pool_t *pool; |
| |
| pool = pj_pool_create(pf, "ffmpeg_cap_dev", 1000, 1000, NULL); |
| f = PJ_POOL_ZALLOC_T(pool, ffmpeg_factory); |
| |
| f->pool = pool; |
| f->pf = pf; |
| f->base.op = &factory_op; |
| |
| avdevice_register_all(); |
| |
| return &f->base; |
| } |
| |
| |
| /* API: init factory */ |
| static pj_status_t ffmpeg_factory_init(pjmedia_vid_dev_factory *f) |
| { |
| return ffmpeg_factory_refresh(f); |
| } |
| |
| /* API: destroy factory */ |
| static pj_status_t ffmpeg_factory_destroy(pjmedia_vid_dev_factory *f) |
| { |
| ffmpeg_factory *ff = (ffmpeg_factory*)f; |
| pj_pool_t *pool = ff->pool; |
| |
| ff->dev_count = 0; |
| ff->pool = NULL; |
| if (ff->dev_pool) |
| pj_pool_release(ff->dev_pool); |
| if (pool) |
| pj_pool_release(pool); |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* API: refresh the list of devices */ |
| static pj_status_t ffmpeg_factory_refresh(pjmedia_vid_dev_factory *f) |
| { |
| ffmpeg_factory *ff = (ffmpeg_factory*)f; |
| AVInputFormat *p; |
| ffmpeg_dev_info *info; |
| |
| av_log_set_callback(&print_ffmpeg_log); |
| av_log_set_level(AV_LOG_DEBUG); |
| |
| if (ff->dev_pool) { |
| pj_pool_release(ff->dev_pool); |
| ff->dev_pool = NULL; |
| } |
| |
| /* TODO: this should enumerate devices, now it enumerates host APIs */ |
| ff->dev_count = 0; |
| ff->dev_pool = pj_pool_create(ff->pf, "ffmpeg_cap_dev", 500, 500, NULL); |
| |
| p = av_iformat_next(NULL); |
| while (p) { |
| if (p->flags & AVFMT_NOFILE) { |
| unsigned i; |
| |
| info = &ff->dev_info[ff->dev_count++]; |
| pj_bzero(info, sizeof(*info)); |
| pj_ansi_strncpy(info->base.name, "default", |
| sizeof(info->base.name)); |
| pj_ansi_snprintf(info->base.driver, sizeof(info->base.driver), |
| "%s (ffmpeg)", p->name); |
| info->base.dir = PJMEDIA_DIR_CAPTURE; |
| info->base.has_callback = PJ_FALSE; |
| |
| info->host_api = p; |
| |
| #if (defined(PJ_WIN32) && PJ_WIN32!=0) || \ |
| (defined(PJ_WIN64) && PJ_WIN64!=0) |
| info->def_devname = "0"; |
| #elif defined(PJ_LINUX) && PJ_LINUX!=0 |
| info->def_devname = "/dev/video0"; |
| #endif |
| |
| /* Set supported formats, currently hardcoded to RGB24 only */ |
| info->base.caps = PJMEDIA_VID_DEV_CAP_FORMAT; |
| info->base.fmt_cnt = 1; |
| for (i = 0; i < info->base.fmt_cnt; ++i) { |
| pjmedia_format *fmt = &info->base.fmt[i]; |
| |
| fmt->id = PJMEDIA_FORMAT_RGB24; |
| fmt->type = PJMEDIA_TYPE_VIDEO; |
| fmt->detail_type = PJMEDIA_FORMAT_DETAIL_NONE; |
| } |
| } |
| p = av_iformat_next(p); |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* API: get number of devices */ |
| static unsigned ffmpeg_factory_get_dev_count(pjmedia_vid_dev_factory *f) |
| { |
| ffmpeg_factory *ff = (ffmpeg_factory*)f; |
| return ff->dev_count; |
| } |
| |
| /* API: get device info */ |
| static pj_status_t ffmpeg_factory_get_dev_info(pjmedia_vid_dev_factory *f, |
| unsigned index, |
| pjmedia_vid_dev_info *info) |
| { |
| ffmpeg_factory *ff = (ffmpeg_factory*)f; |
| |
| PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV); |
| |
| pj_memcpy(info, &ff->dev_info[index].base, sizeof(*info)); |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* API: create default device parameter */ |
| static pj_status_t ffmpeg_factory_default_param(pj_pool_t *pool, |
| pjmedia_vid_dev_factory *f, |
| unsigned index, |
| pjmedia_vid_dev_param *param) |
| { |
| ffmpeg_factory *ff = (ffmpeg_factory*)f; |
| ffmpeg_dev_info *info; |
| |
| PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV); |
| |
| PJ_UNUSED_ARG(pool); |
| |
| info = &ff->dev_info[index]; |
| |
| pj_bzero(param, sizeof(*param)); |
| param->dir = PJMEDIA_DIR_CAPTURE; |
| param->cap_id = index; |
| param->rend_id = PJMEDIA_VID_INVALID_DEV; |
| param->clock_rate = 0; |
| |
| /* Set the device capabilities here */ |
| param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; |
| param->clock_rate = 90000; |
| pjmedia_format_init_video(¶m->fmt, 0, 320, 240, 25, 1); |
| param->fmt.id = info->base.fmt[0].id; |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| |
| /* API: create stream */ |
| static pj_status_t ffmpeg_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) |
| { |
| ffmpeg_factory *ff = (ffmpeg_factory*)f; |
| pj_pool_t *pool; |
| ffmpeg_stream *strm; |
| |
| PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); |
| PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE, PJ_EINVAL); |
| PJ_ASSERT_RETURN((unsigned)param->cap_id < ff->dev_count, PJ_EINVAL); |
| PJ_ASSERT_RETURN(param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO, |
| PJ_EINVAL); |
| |
| PJ_UNUSED_ARG(cb); |
| PJ_UNUSED_ARG(user_data); |
| |
| /* Create and Initialize stream descriptor */ |
| pool = pj_pool_create(ff->pf, "ffmpeg-dev", 1000, 1000, NULL); |
| PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); |
| |
| strm = PJ_POOL_ZALLOC_T(pool, struct ffmpeg_stream); |
| strm->factory = (ffmpeg_factory*)f; |
| strm->pool = pool; |
| pj_memcpy(&strm->param, param, sizeof(*param)); |
| |
| /* Done */ |
| strm->base.op = &stream_op; |
| *p_vid_strm = &strm->base; |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* API: Get stream info. */ |
| static pj_status_t ffmpeg_stream_get_param(pjmedia_vid_dev_stream *s, |
| pjmedia_vid_dev_param *pi) |
| { |
| ffmpeg_stream *strm = (ffmpeg_stream*)s; |
| |
| PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); |
| |
| pj_memcpy(pi, &strm->param, sizeof(*pi)); |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* API: get capability */ |
| static pj_status_t ffmpeg_stream_get_cap(pjmedia_vid_dev_stream *s, |
| pjmedia_vid_dev_cap cap, |
| void *pval) |
| { |
| ffmpeg_stream *strm = (ffmpeg_stream*)s; |
| |
| PJ_UNUSED_ARG(strm); |
| PJ_UNUSED_ARG(cap); |
| PJ_UNUSED_ARG(pval); |
| |
| return PJMEDIA_EVID_INVCAP; |
| } |
| |
| /* API: set capability */ |
| static pj_status_t ffmpeg_stream_set_cap(pjmedia_vid_dev_stream *s, |
| pjmedia_vid_dev_cap cap, |
| const void *pval) |
| { |
| ffmpeg_stream *strm = (ffmpeg_stream*)s; |
| |
| PJ_UNUSED_ARG(strm); |
| PJ_UNUSED_ARG(cap); |
| PJ_UNUSED_ARG(pval); |
| |
| return PJMEDIA_EVID_INVCAP; |
| } |
| |
| |
| /* API: Start stream. */ |
| static pj_status_t ffmpeg_stream_start(pjmedia_vid_dev_stream *s) |
| { |
| ffmpeg_stream *strm = (ffmpeg_stream*)s; |
| ffmpeg_dev_info *info; |
| pj_status_t status; |
| |
| info = &strm->factory->dev_info[strm->param.cap_id]; |
| |
| PJ_LOG(4, (THIS_FILE, "Starting ffmpeg capture stream")); |
| |
| status = ffmpeg_capture_open(&strm->ff_fmt_ctx, info->host_api, |
| info->def_devname, &strm->param); |
| if (status != PJ_SUCCESS) { |
| /* must set ffmpeg states to NULL on any failure */ |
| strm->ff_fmt_ctx = NULL; |
| } |
| |
| return status; |
| } |
| |
| |
| /* API: Get frame from stream */ |
| static pj_status_t ffmpeg_stream_get_frame(pjmedia_vid_dev_stream *s, |
| pjmedia_frame *frame) |
| { |
| ffmpeg_stream *strm = (ffmpeg_stream*)s; |
| AVPacket p; |
| int err; |
| |
| err = av_read_frame(strm->ff_fmt_ctx, &p); |
| if (err < 0) { |
| print_ffmpeg_err(err); |
| return PJ_EUNKNOWN; |
| } |
| |
| pj_bzero(frame, sizeof(*frame)); |
| frame->type = PJMEDIA_FRAME_TYPE_VIDEO; |
| frame->buf = p.data; |
| frame->size = p.size; |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* API: Stop stream. */ |
| static pj_status_t ffmpeg_stream_stop(pjmedia_vid_dev_stream *s) |
| { |
| ffmpeg_stream *strm = (ffmpeg_stream*)s; |
| |
| PJ_LOG(4, (THIS_FILE, "Stopping ffmpeg capture stream")); |
| |
| ffmpeg_capture_close(strm->ff_fmt_ctx); |
| strm->ff_fmt_ctx = NULL; |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* API: Destroy stream. */ |
| static pj_status_t ffmpeg_stream_destroy(pjmedia_vid_dev_stream *s) |
| { |
| ffmpeg_stream *strm = (ffmpeg_stream*)s; |
| |
| PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL); |
| |
| ffmpeg_stream_stop(s); |
| |
| pj_pool_release(strm->pool); |
| |
| return PJ_SUCCESS; |
| } |
| |
| #ifdef _MSC_VER |
| # pragma comment( lib, "avdevice.lib") |
| # pragma comment( lib, "avformat.lib") |
| # pragma comment( lib, "avutil.lib") |
| #endif |
| |
| |
| #endif /* PJMEDIA_VIDEO_DEV_HAS_FFMPEG */ |