win-dshow: Add C985/C353 hardware h264 encoders
This adds support for the AverMedia C985 encoder (which is available on C985 capture cards) as well as the C353 hardware encoder (which is currently available on the X99S Gaming 9 motherboards). These encoders have some limitations, such as limited resolutions (1280x720 and 1024x768), a max GOP size of 30, and the encoder format only supports YV12, which requires conversion if the current output format isn't the same. The C985 and C353 encoders seem to be pretty much identical, although it seems like the C353 has a bit more efficient encoding. I don't believe these are really suitable for streaming, as they do not really have the encoding efficiency needed to stream at lower bitrates, and seem to only support variable bitrate. However, for recording these encoders are quite nice to have available, and work quite well.master
parent
bbff0eeeb9
commit
eb4a05667f
|
@ -9,6 +9,7 @@ set(win-dshow_HEADERS
|
|||
|
||||
set(win-dshow_SOURCES
|
||||
win-dshow.cpp
|
||||
win-dshow-encoder.cpp
|
||||
dshow-plugin.cpp
|
||||
ffmpeg-decode.c)
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# video capture device text
|
||||
VideoCaptureDevice="Video Capture Device"
|
||||
Device="Device"
|
||||
ConfigureAudio="Configure Audio"
|
||||
|
@ -14,3 +15,6 @@ VideoFormat.Any="Any"
|
|||
VideoFormat.Unknown="Unknown (%1)"
|
||||
UseCustomAudioDevice="Use custom audio device"
|
||||
AudioDevice="Audio Device"
|
||||
|
||||
# encoder text
|
||||
Bitrate="Bitrate"
|
||||
|
|
|
@ -4,9 +4,11 @@ OBS_DECLARE_MODULE()
|
|||
OBS_MODULE_USE_DEFAULT_LOCALE("win-dshow", "en-US")
|
||||
|
||||
extern void RegisterDShowSource();
|
||||
extern void RegisterDShowEncoders();
|
||||
|
||||
bool obs_module_load(void)
|
||||
{
|
||||
RegisterDShowSource();
|
||||
RegisterDShowEncoders();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,361 @@
|
|||
#include <obs-module.h>
|
||||
#include <obs-avc.h>
|
||||
#include <util/darray.h>
|
||||
#include <util/dstr.hpp>
|
||||
#include "libdshowcapture/dshowcapture.hpp"
|
||||
|
||||
using namespace DShow;
|
||||
using namespace std;
|
||||
|
||||
struct DShowEncoder {
|
||||
obs_encoder_t *context;
|
||||
VideoEncoder encoder;
|
||||
|
||||
VideoEncoderConfig config;
|
||||
|
||||
const wchar_t *device;
|
||||
video_format format;
|
||||
long long frameInterval;
|
||||
|
||||
bool first = true;
|
||||
DARRAY(uint8_t) firstPacket;
|
||||
DARRAY(uint8_t) header;
|
||||
|
||||
inline DShowEncoder(obs_encoder_t *context_, const wchar_t *device_)
|
||||
: context(context_),
|
||||
device(device_)
|
||||
{
|
||||
da_init(firstPacket);
|
||||
da_init(header);
|
||||
}
|
||||
|
||||
inline ~DShowEncoder()
|
||||
{
|
||||
da_free(firstPacket);
|
||||
da_free(header);
|
||||
}
|
||||
|
||||
inline void ParseFirstPacket(const uint8_t *data, size_t size);
|
||||
|
||||
inline bool Update(obs_data_t *settings);
|
||||
inline bool Encode(struct encoder_frame *frame,
|
||||
struct encoder_packet *packet, bool *received_packet);
|
||||
};
|
||||
|
||||
static const char *GetDShowEncoderName(void)
|
||||
{
|
||||
return "DShow Encoder (temp)";
|
||||
}
|
||||
|
||||
static inline void FindDevice(DeviceId &id, const wchar_t *name)
|
||||
{
|
||||
vector<DeviceId> devices;
|
||||
DShow::VideoEncoder::EnumEncoders(devices);
|
||||
|
||||
for (const DeviceId &device : devices) {
|
||||
if (device.name.find(name) != string::npos) {
|
||||
id = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* As far as I can tell the devices only seem to support 1024x768 and 1280x720
|
||||
* resolutions, so I'm just going to cap it to those resolutions by aspect.
|
||||
*
|
||||
* XXX: This should really not be hard-coded. Problem is I don't know how to
|
||||
* properly query the encoding capabilities of the devices.
|
||||
*/
|
||||
static const double standardAspect = 1024.0 / 768.0;
|
||||
static const double wideAspect = 1280.0 / 720.0;
|
||||
|
||||
inline bool DShowEncoder::Update(obs_data_t *settings)
|
||||
{
|
||||
DStr deviceName;
|
||||
DeviceId id;
|
||||
|
||||
FindDevice(id, device);
|
||||
|
||||
video_t *video = obs_encoder_video(context);
|
||||
const struct video_output_info *voi = video_output_get_info(video);
|
||||
|
||||
int bitrate = (int)obs_data_get_int(settings, "bitrate");
|
||||
int keyint_sec = (int)obs_data_get_int(settings, "keyint_sec");
|
||||
int width = (int)obs_encoder_get_width(context);
|
||||
int height = (int)obs_encoder_get_height(context);
|
||||
|
||||
double aspect = double(width) / double(height);
|
||||
|
||||
if (keyint_sec == 0)
|
||||
keyint_sec = 2;
|
||||
if (fabs(aspect - standardAspect) < fabs(aspect - wideAspect)) {
|
||||
width = 1024;
|
||||
height = 768;
|
||||
} else {
|
||||
width = 1280;
|
||||
height = 720;
|
||||
}
|
||||
|
||||
int keyint = keyint_sec * voi->fps_num / voi->fps_den;
|
||||
|
||||
frameInterval = voi->fps_den * 10000000 / voi->fps_num;
|
||||
|
||||
config.fpsNumerator = voi->fps_num;
|
||||
config.fpsDenominator = voi->fps_den;
|
||||
config.bitrate = bitrate;
|
||||
config.keyframeInterval = keyint;
|
||||
config.cx = width;
|
||||
config.cy = height;
|
||||
config.name = id.name;
|
||||
config.path = id.path;
|
||||
|
||||
first = true;
|
||||
da_resize(firstPacket, 0);
|
||||
da_resize(header, 0);
|
||||
|
||||
dstr_from_wcs(deviceName, id.name.c_str());
|
||||
|
||||
DStr encoder_name;
|
||||
dstr_from_wcs(encoder_name, config.name.c_str());
|
||||
blog(LOG_DEBUG, "win-dshow-encoder:\n"
|
||||
"\tencoder: %s\n"
|
||||
"\twidth: %d\n"
|
||||
"\theight: %d\n"
|
||||
"\tfps_num: %d\n"
|
||||
"\tfps_den: %d",
|
||||
deviceName->array,
|
||||
(int)width,
|
||||
(int)height,
|
||||
(int)voi->fps_num,
|
||||
(int)voi->fps_den);
|
||||
|
||||
return encoder.SetConfig(config);
|
||||
}
|
||||
|
||||
static bool UpdateDShowEncoder(void *data, obs_data_t *settings)
|
||||
{
|
||||
return reinterpret_cast<DShowEncoder*>(data)->Update(settings);
|
||||
}
|
||||
|
||||
static inline void *CreateDShowEncoder(obs_data_t *settings,
|
||||
obs_encoder_t *context, const wchar_t *device)
|
||||
{
|
||||
DShowEncoder *encoder = nullptr;
|
||||
|
||||
try {
|
||||
encoder = new DShowEncoder(context, device);
|
||||
UpdateDShowEncoder(encoder, settings);
|
||||
|
||||
} catch (const char *error) {
|
||||
blog(LOG_ERROR, "Could not create DirectShow encoder '%s': %s",
|
||||
obs_encoder_get_name(context), error);
|
||||
}
|
||||
|
||||
UNUSED_PARAMETER(settings);
|
||||
return encoder;
|
||||
}
|
||||
|
||||
static void *CreateC985Encoder(obs_data_t *settings, obs_encoder_t *context)
|
||||
{
|
||||
return CreateDShowEncoder(settings, context, L"C985");
|
||||
}
|
||||
|
||||
static void *CreateC353Encoder(obs_data_t *settings, obs_encoder_t *context)
|
||||
{
|
||||
return CreateDShowEncoder(settings, context, L"C353");
|
||||
}
|
||||
|
||||
static void DestroyDShowEncoder(void *data)
|
||||
{
|
||||
delete reinterpret_cast<DShowEncoder*>(data);
|
||||
}
|
||||
|
||||
/* the first packet contains the SPS/PPS (header) NALs, so parse the first
|
||||
* packet and separate the NALs */
|
||||
inline void DShowEncoder::ParseFirstPacket(const uint8_t *data, size_t size)
|
||||
{
|
||||
const uint8_t *nal_start, *nal_end, *nal_codestart;
|
||||
const uint8_t *end = data + size;
|
||||
int type;
|
||||
|
||||
nal_start = obs_avc_find_startcode(data, end);
|
||||
nal_end = nullptr;
|
||||
while (nal_end != end) {
|
||||
nal_codestart = nal_start;
|
||||
|
||||
while (nal_start < end && !*(nal_start++));
|
||||
|
||||
if (nal_start == end)
|
||||
break;
|
||||
|
||||
type = nal_start[0] & 0x1F;
|
||||
|
||||
nal_end = obs_avc_find_startcode(nal_start, end);
|
||||
if (!nal_end)
|
||||
nal_end = end;
|
||||
|
||||
if (type == OBS_NAL_SPS || type == OBS_NAL_PPS) {
|
||||
da_push_back_array(header, nal_codestart,
|
||||
nal_end - nal_codestart);
|
||||
|
||||
} else {
|
||||
da_push_back_array(firstPacket, nal_codestart,
|
||||
nal_end - nal_codestart);
|
||||
}
|
||||
|
||||
nal_start = nal_end;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool DShowEncoder::Encode(struct encoder_frame *frame,
|
||||
struct encoder_packet *packet, bool *received_packet)
|
||||
{
|
||||
unsigned char *frame_data[DSHOW_MAX_PLANES] = {};
|
||||
size_t frame_sizes[DSHOW_MAX_PLANES] = {};
|
||||
EncoderPacket dshowPacket;
|
||||
bool new_packet = false;
|
||||
|
||||
/* The encoders expect YV12, so swap the chroma planes for encoding */
|
||||
if (format == VIDEO_FORMAT_I420) {
|
||||
frame_data[0] = frame->data[0];
|
||||
frame_data[1] = frame->data[2];
|
||||
frame_data[2] = frame->data[1];
|
||||
frame_sizes[0] = frame->linesize[0] * config.cy;
|
||||
frame_sizes[1] = frame->linesize[2] * config.cy / 2;
|
||||
frame_sizes[2] = frame->linesize[1] * config.cy / 2;
|
||||
}
|
||||
|
||||
long long actualPTS = frame->pts * frameInterval;
|
||||
|
||||
bool success = encoder.Encode(frame_data, frame_sizes,
|
||||
actualPTS, actualPTS + frameInterval,
|
||||
dshowPacket, new_packet);
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
if (new_packet && !!dshowPacket.data && !!dshowPacket.size) {
|
||||
packet->data = dshowPacket.data;
|
||||
packet->size = dshowPacket.size;
|
||||
packet->type = OBS_ENCODER_VIDEO;
|
||||
packet->pts = dshowPacket.pts / frameInterval;
|
||||
packet->dts = dshowPacket.dts / frameInterval;
|
||||
packet->keyframe = obs_avc_keyframe(packet->data, packet->size);
|
||||
|
||||
/* first packet must be parsed in order to retrieve header */
|
||||
if (first) {
|
||||
first = false;
|
||||
ParseFirstPacket(packet->data, packet->size);
|
||||
packet->data = firstPacket.array;
|
||||
packet->size = firstPacket.num;
|
||||
}
|
||||
|
||||
*received_packet = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DShowEncode(void *data, struct encoder_frame *frame,
|
||||
struct encoder_packet *packet, bool *received_packet)
|
||||
{
|
||||
return reinterpret_cast<DShowEncoder*>(data)->Encode(frame, packet,
|
||||
received_packet);
|
||||
}
|
||||
|
||||
static bool GetDShowExtraData(void *data, uint8_t **extra_data, size_t *size)
|
||||
{
|
||||
DShowEncoder *encoder = reinterpret_cast<DShowEncoder*>(data);
|
||||
|
||||
*extra_data = encoder->header.array;
|
||||
*size = encoder->header.num;
|
||||
|
||||
return *size > 0;
|
||||
}
|
||||
|
||||
static inline bool ValidResolution(uint32_t width, uint32_t height)
|
||||
{
|
||||
return (width == 1280 && height == 720) ||
|
||||
(width == 1024 && height == 768);
|
||||
}
|
||||
|
||||
static bool GetDShowVideoInfo(void *data, struct video_scale_info *info)
|
||||
{
|
||||
DShowEncoder *encoder = reinterpret_cast<DShowEncoder*>(data);
|
||||
video_t *video = obs_encoder_video(encoder->context);
|
||||
const struct video_output_info *vid_info = video_output_get_info(video);
|
||||
|
||||
encoder->format = VIDEO_FORMAT_I420;
|
||||
|
||||
if (vid_info->format == VIDEO_FORMAT_I420 &&
|
||||
ValidResolution(vid_info->width, vid_info->height))
|
||||
return false;
|
||||
|
||||
info->format = VIDEO_FORMAT_I420;
|
||||
info->width = vid_info->width;
|
||||
info->height = vid_info->height;
|
||||
info->range = VIDEO_RANGE_DEFAULT;
|
||||
info->colorspace = VIDEO_CS_DEFAULT;
|
||||
|
||||
double aspect = double(info->width) / double(info->height);
|
||||
|
||||
if (fabs(aspect - standardAspect) < fabs(aspect - wideAspect)) {
|
||||
info->width = 1024;
|
||||
info->height = 768;
|
||||
} else {
|
||||
info->width = 1280;
|
||||
info->height = 720;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void GetDShowEncoderDefauts(obs_data_t *settings)
|
||||
{
|
||||
obs_data_set_default_int(settings, "bitrate", 1000);
|
||||
}
|
||||
|
||||
static obs_properties_t *GetDShowEncoderProperties(void *data)
|
||||
{
|
||||
obs_properties_t *ppts = obs_properties_create();
|
||||
|
||||
obs_properties_add_int(ppts, "bitrate", obs_module_text("Bitrate"),
|
||||
1000, 60000, 1);
|
||||
|
||||
UNUSED_PARAMETER(data);
|
||||
return ppts;
|
||||
}
|
||||
|
||||
void RegisterDShowEncoders()
|
||||
{
|
||||
obs_encoder_info info = {};
|
||||
info.type = OBS_ENCODER_VIDEO;
|
||||
info.codec = "h264";
|
||||
info.get_name = GetDShowEncoderName;
|
||||
info.destroy = DestroyDShowEncoder;
|
||||
info.encode = DShowEncode;
|
||||
info.update = UpdateDShowEncoder;
|
||||
info.get_defaults = GetDShowEncoderDefauts;
|
||||
info.get_properties = GetDShowEncoderProperties;
|
||||
info.get_extra_data = GetDShowExtraData;
|
||||
info.get_video_info = GetDShowVideoInfo;
|
||||
|
||||
vector<DeviceId> devices;
|
||||
DShow::VideoEncoder::EnumEncoders(devices);
|
||||
|
||||
bool foundC985 = false;
|
||||
|
||||
for (const DeviceId &device : devices) {
|
||||
if (!foundC985 && device.name.find(L"C985") != string::npos) {
|
||||
info.id = "dshow_c985_h264";
|
||||
info.create = CreateC985Encoder;
|
||||
obs_register_encoder(&info);
|
||||
foundC985 = true;
|
||||
|
||||
} else if (device.name.find(L"C353") != string::npos) {
|
||||
info.id = "dshow_c353_h264";
|
||||
info.create = CreateC353Encoder;
|
||||
obs_register_encoder(&info);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue