mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2024-11-26 19:01:44 +02:00
a444863738
The implementation will use some default in this case. The empty string is not a meaningful device for any existing hardware type, and indeed OpenCL treats it identically to no device already to work around the lack of this setting on the command line.
483 lines
13 KiB
C
483 lines
13 KiB
C
/*
|
|
* This file is part of FFmpeg.
|
|
*
|
|
* FFmpeg is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* FFmpeg 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with FFmpeg; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include "libavutil/avstring.h"
|
|
|
|
#include "ffmpeg.h"
|
|
|
|
static int nb_hw_devices;
|
|
static HWDevice **hw_devices;
|
|
|
|
static HWDevice *hw_device_get_by_type(enum AVHWDeviceType type)
|
|
{
|
|
HWDevice *found = NULL;
|
|
int i;
|
|
for (i = 0; i < nb_hw_devices; i++) {
|
|
if (hw_devices[i]->type == type) {
|
|
if (found)
|
|
return NULL;
|
|
found = hw_devices[i];
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
HWDevice *hw_device_get_by_name(const char *name)
|
|
{
|
|
int i;
|
|
for (i = 0; i < nb_hw_devices; i++) {
|
|
if (!strcmp(hw_devices[i]->name, name))
|
|
return hw_devices[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static HWDevice *hw_device_add(void)
|
|
{
|
|
int err;
|
|
err = av_reallocp_array(&hw_devices, nb_hw_devices + 1,
|
|
sizeof(*hw_devices));
|
|
if (err) {
|
|
nb_hw_devices = 0;
|
|
return NULL;
|
|
}
|
|
hw_devices[nb_hw_devices] = av_mallocz(sizeof(HWDevice));
|
|
if (!hw_devices[nb_hw_devices])
|
|
return NULL;
|
|
return hw_devices[nb_hw_devices++];
|
|
}
|
|
|
|
static char *hw_device_default_name(enum AVHWDeviceType type)
|
|
{
|
|
// Make an automatic name of the form "type%d". We arbitrarily
|
|
// limit at 1000 anonymous devices of the same type - there is
|
|
// probably something else very wrong if you get to this limit.
|
|
const char *type_name = av_hwdevice_get_type_name(type);
|
|
char *name;
|
|
size_t index_pos;
|
|
int index, index_limit = 1000;
|
|
index_pos = strlen(type_name);
|
|
name = av_malloc(index_pos + 4);
|
|
if (!name)
|
|
return NULL;
|
|
for (index = 0; index < index_limit; index++) {
|
|
snprintf(name, index_pos + 4, "%s%d", type_name, index);
|
|
if (!hw_device_get_by_name(name))
|
|
break;
|
|
}
|
|
if (index >= index_limit) {
|
|
av_freep(&name);
|
|
return NULL;
|
|
}
|
|
return name;
|
|
}
|
|
|
|
int hw_device_init_from_string(const char *arg, HWDevice **dev_out)
|
|
{
|
|
// "type=name:device,key=value,key2=value2"
|
|
// "type:device,key=value,key2=value2"
|
|
// -> av_hwdevice_ctx_create()
|
|
// "type=name@name"
|
|
// "type@name"
|
|
// -> av_hwdevice_ctx_create_derived()
|
|
|
|
AVDictionary *options = NULL;
|
|
const char *type_name = NULL, *name = NULL, *device = NULL;
|
|
enum AVHWDeviceType type;
|
|
HWDevice *dev, *src;
|
|
AVBufferRef *device_ref = NULL;
|
|
int err;
|
|
const char *errmsg, *p, *q;
|
|
size_t k;
|
|
|
|
k = strcspn(arg, ":=@");
|
|
p = arg + k;
|
|
|
|
type_name = av_strndup(arg, k);
|
|
if (!type_name) {
|
|
err = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
type = av_hwdevice_find_type_by_name(type_name);
|
|
if (type == AV_HWDEVICE_TYPE_NONE) {
|
|
errmsg = "unknown device type";
|
|
goto invalid;
|
|
}
|
|
|
|
if (*p == '=') {
|
|
k = strcspn(p + 1, ":@");
|
|
|
|
name = av_strndup(p + 1, k);
|
|
if (!name) {
|
|
err = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
if (hw_device_get_by_name(name)) {
|
|
errmsg = "named device already exists";
|
|
goto invalid;
|
|
}
|
|
|
|
p += 1 + k;
|
|
} else {
|
|
name = hw_device_default_name(type);
|
|
if (!name) {
|
|
err = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (!*p) {
|
|
// New device with no parameters.
|
|
err = av_hwdevice_ctx_create(&device_ref, type,
|
|
NULL, NULL, 0);
|
|
if (err < 0)
|
|
goto fail;
|
|
|
|
} else if (*p == ':') {
|
|
// New device with some parameters.
|
|
++p;
|
|
q = strchr(p, ',');
|
|
if (q) {
|
|
if (q - p > 0) {
|
|
device = av_strndup(p, q - p);
|
|
if (!device) {
|
|
err = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
}
|
|
err = av_dict_parse_string(&options, q + 1, "=", ",", 0);
|
|
if (err < 0) {
|
|
errmsg = "failed to parse options";
|
|
goto invalid;
|
|
}
|
|
}
|
|
|
|
err = av_hwdevice_ctx_create(&device_ref, type,
|
|
q ? device : p[0] ? p : NULL,
|
|
options, 0);
|
|
if (err < 0)
|
|
goto fail;
|
|
|
|
} else if (*p == '@') {
|
|
// Derive from existing device.
|
|
|
|
src = hw_device_get_by_name(p + 1);
|
|
if (!src) {
|
|
errmsg = "invalid source device name";
|
|
goto invalid;
|
|
}
|
|
|
|
err = av_hwdevice_ctx_create_derived(&device_ref, type,
|
|
src->device_ref, 0);
|
|
if (err < 0)
|
|
goto fail;
|
|
} else {
|
|
errmsg = "parse error";
|
|
goto invalid;
|
|
}
|
|
|
|
dev = hw_device_add();
|
|
if (!dev) {
|
|
err = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
|
|
dev->name = name;
|
|
dev->type = type;
|
|
dev->device_ref = device_ref;
|
|
|
|
if (dev_out)
|
|
*dev_out = dev;
|
|
|
|
name = NULL;
|
|
err = 0;
|
|
done:
|
|
av_freep(&type_name);
|
|
av_freep(&name);
|
|
av_freep(&device);
|
|
av_dict_free(&options);
|
|
return err;
|
|
invalid:
|
|
av_log(NULL, AV_LOG_ERROR,
|
|
"Invalid device specification \"%s\": %s\n", arg, errmsg);
|
|
err = AVERROR(EINVAL);
|
|
goto done;
|
|
fail:
|
|
av_log(NULL, AV_LOG_ERROR,
|
|
"Device creation failed: %d.\n", err);
|
|
av_buffer_unref(&device_ref);
|
|
goto done;
|
|
}
|
|
|
|
static int hw_device_init_from_type(enum AVHWDeviceType type,
|
|
const char *device,
|
|
HWDevice **dev_out)
|
|
{
|
|
AVBufferRef *device_ref = NULL;
|
|
HWDevice *dev;
|
|
char *name;
|
|
int err;
|
|
|
|
name = hw_device_default_name(type);
|
|
if (!name) {
|
|
err = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
|
|
err = av_hwdevice_ctx_create(&device_ref, type, device, NULL, 0);
|
|
if (err < 0) {
|
|
av_log(NULL, AV_LOG_ERROR,
|
|
"Device creation failed: %d.\n", err);
|
|
goto fail;
|
|
}
|
|
|
|
dev = hw_device_add();
|
|
if (!dev) {
|
|
err = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
|
|
dev->name = name;
|
|
dev->type = type;
|
|
dev->device_ref = device_ref;
|
|
|
|
if (dev_out)
|
|
*dev_out = dev;
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
av_freep(&name);
|
|
av_buffer_unref(&device_ref);
|
|
return err;
|
|
}
|
|
|
|
void hw_device_free_all(void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < nb_hw_devices; i++) {
|
|
av_freep(&hw_devices[i]->name);
|
|
av_buffer_unref(&hw_devices[i]->device_ref);
|
|
av_freep(&hw_devices[i]);
|
|
}
|
|
av_freep(&hw_devices);
|
|
nb_hw_devices = 0;
|
|
}
|
|
|
|
static HWDevice *hw_device_match_by_codec(const AVCodec *codec)
|
|
{
|
|
const AVCodecHWConfig *config;
|
|
HWDevice *dev;
|
|
int i;
|
|
for (i = 0;; i++) {
|
|
config = avcodec_get_hw_config(codec, i);
|
|
if (!config)
|
|
return NULL;
|
|
if (!(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX))
|
|
continue;
|
|
dev = hw_device_get_by_type(config->device_type);
|
|
if (dev)
|
|
return dev;
|
|
}
|
|
}
|
|
|
|
int hw_device_setup_for_decode(InputStream *ist)
|
|
{
|
|
const AVCodecHWConfig *config;
|
|
enum AVHWDeviceType type;
|
|
HWDevice *dev = NULL;
|
|
int err, auto_device = 0;
|
|
|
|
if (ist->hwaccel_device) {
|
|
dev = hw_device_get_by_name(ist->hwaccel_device);
|
|
if (!dev) {
|
|
if (ist->hwaccel_id == HWACCEL_AUTO) {
|
|
auto_device = 1;
|
|
} else if (ist->hwaccel_id == HWACCEL_GENERIC) {
|
|
type = ist->hwaccel_device_type;
|
|
err = hw_device_init_from_type(type, ist->hwaccel_device,
|
|
&dev);
|
|
} else {
|
|
// This will be dealt with by API-specific initialisation
|
|
// (using hwaccel_device), so nothing further needed here.
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (ist->hwaccel_id == HWACCEL_AUTO) {
|
|
ist->hwaccel_device_type = dev->type;
|
|
} else if (ist->hwaccel_device_type != dev->type) {
|
|
av_log(ist->dec_ctx, AV_LOG_ERROR, "Invalid hwaccel device "
|
|
"specified for decoder: device %s of type %s is not "
|
|
"usable with hwaccel %s.\n", dev->name,
|
|
av_hwdevice_get_type_name(dev->type),
|
|
av_hwdevice_get_type_name(ist->hwaccel_device_type));
|
|
return AVERROR(EINVAL);
|
|
}
|
|
}
|
|
} else {
|
|
if (ist->hwaccel_id == HWACCEL_AUTO) {
|
|
auto_device = 1;
|
|
} else if (ist->hwaccel_id == HWACCEL_GENERIC) {
|
|
type = ist->hwaccel_device_type;
|
|
dev = hw_device_get_by_type(type);
|
|
if (!dev)
|
|
err = hw_device_init_from_type(type, NULL, &dev);
|
|
} else {
|
|
dev = hw_device_match_by_codec(ist->dec);
|
|
if (!dev) {
|
|
// No device for this codec, but not using generic hwaccel
|
|
// and therefore may well not need one - ignore.
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (auto_device) {
|
|
int i;
|
|
if (!avcodec_get_hw_config(ist->dec, 0)) {
|
|
// Decoder does not support any hardware devices.
|
|
return 0;
|
|
}
|
|
for (i = 0; !dev; i++) {
|
|
config = avcodec_get_hw_config(ist->dec, i);
|
|
if (!config)
|
|
break;
|
|
type = config->device_type;
|
|
dev = hw_device_get_by_type(type);
|
|
if (dev) {
|
|
av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto "
|
|
"hwaccel type %s with existing device %s.\n",
|
|
av_hwdevice_get_type_name(type), dev->name);
|
|
}
|
|
}
|
|
for (i = 0; !dev; i++) {
|
|
config = avcodec_get_hw_config(ist->dec, i);
|
|
if (!config)
|
|
break;
|
|
type = config->device_type;
|
|
// Try to make a new device of this type.
|
|
err = hw_device_init_from_type(type, ist->hwaccel_device,
|
|
&dev);
|
|
if (err < 0) {
|
|
// Can't make a device of this type.
|
|
continue;
|
|
}
|
|
if (ist->hwaccel_device) {
|
|
av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto "
|
|
"hwaccel type %s with new device created "
|
|
"from %s.\n", av_hwdevice_get_type_name(type),
|
|
ist->hwaccel_device);
|
|
} else {
|
|
av_log(ist->dec_ctx, AV_LOG_INFO, "Using auto "
|
|
"hwaccel type %s with new default device.\n",
|
|
av_hwdevice_get_type_name(type));
|
|
}
|
|
}
|
|
if (dev) {
|
|
ist->hwaccel_device_type = type;
|
|
} else {
|
|
av_log(ist->dec_ctx, AV_LOG_INFO, "Auto hwaccel "
|
|
"disabled: no device found.\n");
|
|
ist->hwaccel_id = HWACCEL_NONE;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!dev) {
|
|
av_log(ist->dec_ctx, AV_LOG_ERROR, "No device available "
|
|
"for decoder: device type %s needed for codec %s.\n",
|
|
av_hwdevice_get_type_name(type), ist->dec->name);
|
|
return err;
|
|
}
|
|
|
|
ist->dec_ctx->hw_device_ctx = av_buffer_ref(dev->device_ref);
|
|
if (!ist->dec_ctx->hw_device_ctx)
|
|
return AVERROR(ENOMEM);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int hw_device_setup_for_encode(OutputStream *ost)
|
|
{
|
|
HWDevice *dev;
|
|
|
|
dev = hw_device_match_by_codec(ost->enc);
|
|
if (dev) {
|
|
ost->enc_ctx->hw_device_ctx = av_buffer_ref(dev->device_ref);
|
|
if (!ost->enc_ctx->hw_device_ctx)
|
|
return AVERROR(ENOMEM);
|
|
return 0;
|
|
} else {
|
|
// No device required, or no device available.
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int hwaccel_retrieve_data(AVCodecContext *avctx, AVFrame *input)
|
|
{
|
|
InputStream *ist = avctx->opaque;
|
|
AVFrame *output = NULL;
|
|
enum AVPixelFormat output_format = ist->hwaccel_output_format;
|
|
int err;
|
|
|
|
if (input->format == output_format) {
|
|
// Nothing to do.
|
|
return 0;
|
|
}
|
|
|
|
output = av_frame_alloc();
|
|
if (!output)
|
|
return AVERROR(ENOMEM);
|
|
|
|
output->format = output_format;
|
|
|
|
err = av_hwframe_transfer_data(output, input, 0);
|
|
if (err < 0) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to transfer data to "
|
|
"output frame: %d.\n", err);
|
|
goto fail;
|
|
}
|
|
|
|
err = av_frame_copy_props(output, input);
|
|
if (err < 0) {
|
|
av_frame_unref(output);
|
|
goto fail;
|
|
}
|
|
|
|
av_frame_unref(input);
|
|
av_frame_move_ref(input, output);
|
|
av_frame_free(&output);
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
av_frame_free(&output);
|
|
return err;
|
|
}
|
|
|
|
int hwaccel_decode_init(AVCodecContext *avctx)
|
|
{
|
|
InputStream *ist = avctx->opaque;
|
|
|
|
ist->hwaccel_retrieve_data = &hwaccel_retrieve_data;
|
|
|
|
return 0;
|
|
}
|