blob: 1b657374275048e7a614b5bf6f8eaef4d3d06cce [file] [log] [blame]
/* $Id: ffmpeg_dev.c 4537 2013-06-19 06:47:43Z riza $ */
/*
* 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(&param->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(&param->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 */