Merge pull request #4053 from akallabeth/ffmpeg_encoder

Implemented FFMPEG based encoder.
This commit is contained in:
David Fort 2017-11-06 11:25:48 +01:00 committed by GitHub
commit 504b771686
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 286 additions and 59 deletions

View File

@ -626,9 +626,8 @@ static UINT rdpgfx_write_surface_command(wStream* s,
else if (cmd->codecId == RDPGFX_CODECID_AVC444)
{
havc444 = (RDPGFX_AVC444_BITMAP_STREAM*)cmd->extra;
havc420 = &(havc444->bitstream[0]);
/* avc420EncodedBitstreamInfo (4 bytes) */
Stream_Write_UINT32(s, havc420->length | (havc444->LC << 30UL));
havc420 = &(havc444->bitstream[0]); /* avc420EncodedBitstreamInfo (4 bytes) */
Stream_Write_UINT32(s, havc444->cbAvc420EncodedBitstream1 | (havc444->LC << 30UL));
/* avc420EncodedBitstream1 */
error = rdpgfx_write_h264_avc420(s, havc420);
@ -641,7 +640,7 @@ static UINT rdpgfx_write_surface_command(wStream* s,
/* avc420EncodedBitstream2 */
if (havc444->LC == 0)
{
havc420 = &(havc444->bitstream[0]);
havc420 = &(havc444->bitstream[1]);
error = rdpgfx_write_h264_avc420(s, havc420);
if (error != CHANNEL_RC_OK)

View File

@ -23,11 +23,10 @@
#include <freerdp/codec/h264.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#define TAG FREERDP_TAG("codec")
#warning "FFMPEG does not support H264 encoding (GFX H264 server mode). Please review your configuration!"
/* Fallback support for older libavcodec versions */
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 59, 100)
#define AV_CODEC_ID_H264 CODEC_ID_H264
@ -35,28 +34,126 @@
struct _H264_CONTEXT_LIBAVCODEC
{
AVCodec* codec;
AVCodecContext* codecContext;
AVCodec* codecDecoder;
AVCodecContext* codecDecoderContext;
AVCodec* codecEncoder;
AVCodecContext* codecEncoderContext;
AVCodecParserContext* codecParser;
AVFrame* videoFrame;
AVPacket packet;
};
typedef struct _H264_CONTEXT_LIBAVCODEC H264_CONTEXT_LIBAVCODEC;
static void libavcodec_destroy_encoder(H264_CONTEXT* h264)
{
H264_CONTEXT_LIBAVCODEC* sys;
if (!h264 || !h264->subsystem)
return;
sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData;
if (sys->codecEncoderContext)
{
avcodec_close(sys->codecEncoderContext);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 69, 100)
avcodec_free_context(&sys->codecEncoderContext);
#else
av_free(sys->codecEncoderContext);
#endif
}
sys->codecEncoder = NULL;
sys->codecEncoderContext = NULL;
}
static BOOL libavcodec_create_encoder(H264_CONTEXT* h264)
{
BOOL recreate = FALSE;
H264_CONTEXT_LIBAVCODEC* sys;
if (!h264 || !h264->subsystem)
return FALSE;
sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData;
recreate = !sys->codecEncoder || !sys->codecEncoderContext;
if (sys->codecEncoderContext)
{
if ((sys->codecEncoderContext->width != h264->width) ||
(sys->codecEncoderContext->height != h264->height))
recreate = TRUE;
}
if (!recreate)
return TRUE;
libavcodec_destroy_encoder(h264);
sys->codecEncoder = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!sys->codecEncoder)
goto EXCEPTION;
sys->codecEncoderContext = avcodec_alloc_context3(sys->codecEncoder);
if (!sys->codecEncoderContext)
goto EXCEPTION;
switch (h264->RateControlMode)
{
case H264_RATECONTROL_VBR:
sys->codecEncoderContext->bit_rate = h264->BitRate;
break;
case H264_RATECONTROL_CQP:
/* TODO: sys->codecEncoderContext-> = h264->QP; */
break;
default:
break;
}
sys->codecEncoderContext->width = h264->width;
sys->codecEncoderContext->height = h264->height;
sys->codecEncoderContext->delay = 0;
sys->codecEncoderContext->framerate = (AVRational)
{
h264->FrameRate, 1
};
sys->codecEncoderContext->time_base = (AVRational)
{
1, h264->FrameRate
};
sys->codecEncoderContext->gop_size = 0;
sys->codecEncoderContext->max_b_frames = 0;
sys->codecEncoderContext->pix_fmt = AV_PIX_FMT_YUV420P;
av_opt_set(sys->codecEncoderContext->priv_data, "preset", "fast", 0);
av_opt_set(sys->codecEncoderContext->priv_data, "vprofile", "baseline", 0);
av_opt_set(sys->codecEncoderContext->priv_data, "tune", "zerolatency", 0);
if (avcodec_open2(sys->codecEncoderContext, sys->codecEncoder, NULL) < 0)
goto EXCEPTION;
return TRUE;
EXCEPTION:
libavcodec_destroy_encoder(h264);
return FALSE;
}
static int libavcodec_decompress(H264_CONTEXT* h264, const BYTE* pSrcData,
UINT32 SrcSize)
{
int status;
int gotFrame = 0;
AVPacket packet;
H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*) h264->pSystemData;
BYTE** pYUVData = h264->pYUVData;
UINT32* iStride = h264->iStride;
av_init_packet(&packet);
packet.data = (BYTE*)pSrcData;
packet.size = SrcSize;
av_init_packet(&sys->packet);
sys->packet.data = (BYTE*)pSrcData;
sys->packet.size = SrcSize;
/* avcodec_decode_video2 is deprecated with libavcodec 57.48.101 */
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
status = avcodec_send_packet(sys->codecContext, &packet);
status = avcodec_send_packet(sys->codecDecoderContext, &sys->packet);
if (status < 0)
{
@ -66,14 +163,14 @@ static int libavcodec_decompress(H264_CONTEXT* h264, const BYTE* pSrcData,
do
{
status = avcodec_receive_frame(sys->codecContext, sys->videoFrame);
status = avcodec_receive_frame(sys->codecDecoderContext, sys->videoFrame);
}
while (status == AVERROR(EAGAIN));
gotFrame = (status == 0);
#else
status = avcodec_decode_video2(sys->codecContext, sys->videoFrame, &gotFrame,
&packet);
status = avcodec_decode_video2(sys->codecDecoderContext, sys->videoFrame, &gotFrame,
&sys->packet);
#endif
if (status < 0)
@ -110,7 +207,77 @@ static int libavcodec_decompress(H264_CONTEXT* h264, const BYTE* pSrcData,
static int libavcodec_compress(H264_CONTEXT* h264, BYTE** ppDstData, UINT32* pDstSize)
{
return -1;
int status;
int gotFrame = 0;
H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*) h264->pSystemData;
if (!libavcodec_create_encoder(h264))
return -1;
av_free(sys->packet.data);
av_init_packet(&sys->packet);
sys->packet.data = NULL;
sys->packet.size = 0;
sys->videoFrame->format = sys->codecEncoderContext->pix_fmt;
sys->videoFrame->width = sys->codecEncoderContext->width;
sys->videoFrame->height = sys->codecEncoderContext->height;
sys->videoFrame->colorspace = AVCOL_SPC_BT709;
sys->videoFrame->data[0] = h264->pYUVData[0];
sys->videoFrame->data[1] = h264->pYUVData[1];
sys->videoFrame->data[2] = h264->pYUVData[2];
sys->videoFrame->linesize[0] = h264->iStride[0];
sys->videoFrame->linesize[1] = h264->iStride[1];
sys->videoFrame->linesize[2] = h264->iStride[2];
sys->videoFrame->pts++;
sys->videoFrame->pict_type = AV_PICTURE_TYPE_I;
/* avcodec_encode_video2 is deprecated with libavcodec 57.48.101 */
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
status = avcodec_send_frame(sys->codecEncoderContext, sys->videoFrame);
if (status < 0)
{
WLog_ERR(TAG, "Failed to encode video frame (%s [%d])",
av_err2str(status), status);
return -1;
}
status = avcodec_receive_packet(sys->codecEncoderContext,
&sys->packet);
if (status < 0)
{
WLog_ERR(TAG, "Failed to encode video frame (%s [%d])",
av_err2str(status), status);
return -1;
}
gotFrame = (status == 0);
#else
do
{
status = avcodec_encode_video2(sys->codecEncoderContext,
&sys->packet,
sys->videoFrame, &gotFrame);
}
while ((status >= 0) && (gotFrame != 0));
#endif
if (status < 0)
{
WLog_ERR(TAG, "Failed to encode video frame (%s [%d])",
av_err2str(status), status);
return -1;
}
*ppDstData = sys->packet.data;
*pDstSize = sys->packet.size;
if (!gotFrame)
return -2;
return 1;
}
static void libavcodec_uninit(H264_CONTEXT* h264)
@ -130,20 +297,20 @@ static void libavcodec_uninit(H264_CONTEXT* h264)
}
if (sys->codecParser)
{
av_parser_close(sys->codecParser);
}
if (sys->codecContext)
if (sys->codecDecoderContext)
{
avcodec_close(sys->codecContext);
avcodec_close(sys->codecDecoderContext);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 69, 100)
avcodec_free_context(&sys->codecContext);
avcodec_free_context(&sys->codecDecoderContext);
#else
av_free(sys->codecContext);
av_free(sys->codecDecoderContext);
#endif
}
libavcodec_destroy_encoder(h264);
free(sys);
h264->pSystemData = NULL;
}
@ -160,39 +327,43 @@ static BOOL libavcodec_init(H264_CONTEXT* h264)
h264->pSystemData = (void*) sys;
avcodec_register_all();
sys->codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!sys->codec)
if (!h264->Compressor)
{
WLog_ERR(TAG, "Failed to find libav H.264 codec");
goto EXCEPTION;
}
sys->codecDecoder = avcodec_find_decoder(AV_CODEC_ID_H264);
sys->codecContext = avcodec_alloc_context3(sys->codec);
if (!sys->codecDecoder)
{
WLog_ERR(TAG, "Failed to find libav H.264 codec");
goto EXCEPTION;
}
if (!sys->codecContext)
{
WLog_ERR(TAG, "Failed to allocate libav codec context");
goto EXCEPTION;
}
sys->codecDecoderContext = avcodec_alloc_context3(sys->codecDecoder);
if (sys->codec->capabilities & CODEC_CAP_TRUNCATED)
{
sys->codecContext->flags |= CODEC_FLAG_TRUNCATED;
}
if (!sys->codecDecoderContext)
{
WLog_ERR(TAG, "Failed to allocate libav codec context");
goto EXCEPTION;
}
if (avcodec_open2(sys->codecContext, sys->codec, NULL) < 0)
{
WLog_ERR(TAG, "Failed to open libav codec");
goto EXCEPTION;
}
if (sys->codecDecoder->capabilities & CODEC_CAP_TRUNCATED)
{
sys->codecDecoderContext->flags |= CODEC_FLAG_TRUNCATED;
}
sys->codecParser = av_parser_init(AV_CODEC_ID_H264);
if (avcodec_open2(sys->codecDecoderContext, sys->codecDecoder, NULL) < 0)
{
WLog_ERR(TAG, "Failed to open libav codec");
goto EXCEPTION;
}
if (!sys->codecParser)
{
WLog_ERR(TAG, "Failed to initialize libav parser");
goto EXCEPTION;
sys->codecParser = av_parser_init(AV_CODEC_ID_H264);
if (!sys->codecParser)
{
WLog_ERR(TAG, "Failed to initialize libav parser");
goto EXCEPTION;
}
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 18, 102)
@ -207,6 +378,8 @@ static BOOL libavcodec_init(H264_CONTEXT* h264)
goto EXCEPTION;
}
sys->videoFrame->pts = 0;
return TRUE;
EXCEPTION:
libavcodec_uninit(h264);

View File

@ -644,7 +644,7 @@ static UINT shadow_client_rdpgfx_caps_advertise(RdpgfxServerContext* context,
settings->GfxH264 = FALSE;
pdu.capsSet->flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED;
#else
settings->GfxH264 = !(flags & RDPGFX_CAPS_FLAG_AVC_DISABLED);
settings->GfxAVC444 = settings->GfxH264 = !(flags & RDPGFX_CAPS_FLAG_AVC_DISABLED);
#endif
}
@ -670,7 +670,7 @@ static UINT shadow_client_rdpgfx_caps_advertise(RdpgfxServerContext* context,
settings->GfxH264 = FALSE;
pdu.capsSet->flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED;
#else
settings->GfxH264 = !(flags & RDPGFX_CAPS_FLAG_AVC_DISABLED);
settings->GfxAVC444 = settings->GfxH264 = !(flags & RDPGFX_CAPS_FLAG_AVC_DISABLED);
#endif
}
@ -696,7 +696,7 @@ static UINT shadow_client_rdpgfx_caps_advertise(RdpgfxServerContext* context,
settings->GfxH264 = FALSE;
pdu.capsSet->flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED;
#else
settings->GfxH264 = !(flags & RDPGFX_CAPS_FLAG_AVC_DISABLED);
settings->GfxAVC444 = settings->GfxH264 = !(flags & RDPGFX_CAPS_FLAG_AVC_DISABLED);
#endif
}
@ -717,6 +717,7 @@ static UINT shadow_client_rdpgfx_caps_advertise(RdpgfxServerContext* context,
if (settings)
{
flags = pdu.capsSet->flags;
settings->GfxAVC444 = FALSE;
settings->GfxThinClient = (flags & RDPGFX_CAPS_FLAG_THINCLIENT);
settings->GfxSmallCache = (flags & RDPGFX_CAPS_FLAG_SMALL_CACHE);
#ifndef WITH_GFX_H264
@ -755,6 +756,15 @@ static UINT shadow_client_rdpgfx_caps_advertise(RdpgfxServerContext* context,
return CHANNEL_RC_UNSUPPORTED_VERSION;
}
static INLINE UINT32 rdpgfx_estimate_h264_avc420(
RDPGFX_AVC420_BITMAP_STREAM* havc420)
{
/* H264 metadata + H264 stream. See rdpgfx_write_h264_avc420 */
return sizeof(UINT32) /* numRegionRects */
+ 10 /* regionRects + quantQualityVals */
* havc420->meta.numRegionRects
+ havc420->length;
}
/**
* Function description
@ -801,7 +811,50 @@ static BOOL shadow_client_send_surface_gfx(rdpShadowClient* client,
cmd.data = NULL;
cmd.extra = NULL;
if (settings->GfxH264)
if (settings->GfxAVC444)
{
RDPGFX_AVC444_BITMAP_STREAM avc444;
RECTANGLE_16 regionRect;
RDPGFX_H264_QUANT_QUALITY quantQualityVal;
if (shadow_encoder_prepare(encoder, FREERDP_CODEC_AVC444) < 0)
{
WLog_ERR(TAG, "Failed to prepare encoder FREERDP_CODEC_AVC444");
return FALSE;
}
if (avc420_compress(encoder->h264, pSrcData, cmd.format, nSrcStep,
nWidth, nHeight, &avc444.bitstream[0].data,
&avc444.bitstream[0].length) < 0)
return FALSE;
regionRect.left = cmd.left;
regionRect.top = cmd.top;
regionRect.right = cmd.right;
regionRect.bottom = cmd.bottom;
quantQualityVal.qp = encoder->h264->QP;
quantQualityVal.r = 0;
quantQualityVal.p = 0;
quantQualityVal.qualityVal = 100 - quantQualityVal.qp;
avc444.bitstream[0].meta.numRegionRects = 1;
avc444.bitstream[0].meta.regionRects = &regionRect;
avc444.bitstream[0].meta.quantQualityVals = &quantQualityVal;
avc444.LC = 1;
avc444.cbAvc420EncodedBitstream1 = rdpgfx_estimate_h264_avc420(&avc444.bitstream[0]);
cmd.codecId = RDPGFX_CODECID_AVC444;
cmd.extra = (void*)&avc444;
IFCALLRET(client->rdpgfx->SurfaceFrameCommand, error, client->rdpgfx, &cmd,
&cmdstart, &cmdend);
if (error)
{
WLog_ERR(TAG, "SurfaceFrameCommand failed with error %"PRIu32"", error);
return FALSE;
}
}
else if (settings->GfxH264)
{
RDPGFX_AVC420_BITMAP_STREAM avc420;
RECTANGLE_16 regionRect;
@ -813,8 +866,10 @@ static BOOL shadow_client_send_surface_gfx(rdpShadowClient* client,
return FALSE;
}
avc420_compress(encoder->h264, pSrcData, cmd.format, nSrcStep,
nWidth, nHeight, &avc420.data, &avc420.length);
if (avc420_compress(encoder->h264, pSrcData, cmd.format, nSrcStep,
nWidth, nHeight, &avc420.data, &avc420.length) < 0)
return FALSE;
cmd.codecId = RDPGFX_CODECID_AVC420;
cmd.extra = (void*)&avc420;
regionRect.left = cmd.left;

View File

@ -238,7 +238,7 @@ static int shadow_encoder_init_h264(rdpShadowEncoder* encoder)
encoder->h264->BitRate = encoder->server->h264BitRate;
encoder->h264->FrameRate = encoder->server->h264FrameRate;
encoder->h264->QP = encoder->server->h264QP;
encoder->codecs |= FREERDP_CODEC_AVC420;
encoder->codecs |= FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444;
return 1;
fail:
h264_context_free(encoder->h264);
@ -319,7 +319,7 @@ static int shadow_encoder_uninit_h264(rdpShadowEncoder* encoder)
encoder->h264 = NULL;
}
encoder->codecs &= ~FREERDP_CODEC_AVC420;
encoder->codecs &= ~(FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444);
return 1;
}
@ -353,7 +353,7 @@ static int shadow_encoder_uninit(rdpShadowEncoder* encoder)
shadow_encoder_uninit_interleaved(encoder);
}
if (encoder->codecs & FREERDP_CODEC_AVC420)
if (encoder->codecs & (FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444))
{
shadow_encoder_uninit_h264(encoder);
}
@ -430,8 +430,8 @@ int shadow_encoder_prepare(rdpShadowEncoder* encoder, UINT32 codecs)
return -1;
}
if ((codecs & FREERDP_CODEC_AVC420)
&& !(encoder->codecs & FREERDP_CODEC_AVC420))
if ((codecs & (FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444))
&& !(encoder->codecs & (FREERDP_CODEC_AVC420 | FREERDP_CODEC_AVC444)))
{
status = shadow_encoder_init_h264(encoder);