892 lines
21 KiB
C++
892 lines
21 KiB
C++
/***************************************************************************************
|
|
*
|
|
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
|
*
|
|
* By downloading, copying, installing or using the software you agree to this license.
|
|
* If you do not agree to this license, do not download, install,
|
|
* copy or use the software.
|
|
*
|
|
* Copyright (C) 2014-2022, Happytimesoft Corporation, all rights reserved.
|
|
*
|
|
* Redistribution and use in binary forms, with or without modification, are permitted.
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software distributed
|
|
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
|
* language governing permissions and limitations under the License.
|
|
*
|
|
****************************************************************************************/
|
|
|
|
#include "sys_inc.h"
|
|
#include "video_encoder.h"
|
|
#include "media_format.h"
|
|
#include "base64.h"
|
|
#include "media_util.h"
|
|
#include "media_codec.h"
|
|
#include "h264.h"
|
|
#include "h265.h"
|
|
#include "avcodec_mutex.h"
|
|
|
|
CVideoEncoder::CVideoEncoder()
|
|
{
|
|
m_nCodecId = AV_CODEC_ID_NONE;
|
|
m_nDstPixFmt = AV_PIX_FMT_YUV420P;
|
|
|
|
m_pCodecCtx = NULL;
|
|
m_pFrameSrc = NULL;
|
|
m_pFrameYuv = NULL;
|
|
m_pts = 0;
|
|
|
|
m_nFrameIndex = 0;
|
|
m_bInited = FALSE;
|
|
|
|
m_pCallbackMutex = sys_os_create_mutex();
|
|
m_pCallbackList = h_list_create(FALSE);
|
|
}
|
|
|
|
CVideoEncoder::~CVideoEncoder()
|
|
{
|
|
uninit();
|
|
|
|
h_list_free_container(m_pCallbackList);
|
|
|
|
sys_os_destroy_sig_mutex(m_pCallbackMutex);
|
|
}
|
|
|
|
int CVideoEncoder::computeBitrate(AVCodecID codec, int width, int height, double framerate, int quality)
|
|
{
|
|
double fLQ = 0;
|
|
double fMQ = 0;
|
|
double fHQ = 0;
|
|
|
|
int factor = 2;
|
|
int pixels = width * height;
|
|
|
|
if (quality>= 0 && quality < 33)
|
|
{
|
|
factor = 0;
|
|
}
|
|
else if (quality >= 33 && quality < 67)
|
|
{
|
|
factor = 1;
|
|
}
|
|
else if (quality >= 67 && quality <= 100)
|
|
{
|
|
factor = 2;
|
|
}
|
|
|
|
switch (codec)
|
|
{
|
|
case AV_CODEC_ID_H264:
|
|
if (pixels < 640*480)
|
|
{
|
|
fLQ = 12/70.0;
|
|
fMQ = 12/55.0;
|
|
fHQ = 12/40.0;
|
|
}
|
|
else
|
|
{
|
|
fLQ = 12/90.0;
|
|
fMQ = 12/70.0;
|
|
fHQ = 12/60.0;
|
|
}
|
|
break;
|
|
|
|
case AV_CODEC_ID_MJPEG:
|
|
if (pixels < 640*480)
|
|
{
|
|
fLQ = 12/40.0;
|
|
fMQ = 12/30.0;
|
|
fHQ = 12/20.0;
|
|
}
|
|
else
|
|
{
|
|
fLQ = 12/50.0;
|
|
fMQ = 12/40.0;
|
|
fHQ = 12/30.0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (pixels < 640*480)
|
|
{
|
|
fLQ = 12/55.0;
|
|
fMQ = 12/40.0;
|
|
fHQ = 12/30.0;
|
|
}
|
|
else
|
|
{
|
|
fLQ = 12/75.0;
|
|
fMQ = 12/60.0;
|
|
fHQ = 12/50.0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
int nBitrate = 0;
|
|
|
|
if (0 == factor)
|
|
{
|
|
nBitrate = (int) (pixels * framerate * fLQ);
|
|
}
|
|
else if (1 == factor)
|
|
{
|
|
nBitrate = (int) (pixels * framerate * fMQ);
|
|
}
|
|
else
|
|
{
|
|
nBitrate = (int) (pixels * framerate * fHQ);
|
|
}
|
|
|
|
if (quality < 40)
|
|
{
|
|
quality = 40;
|
|
}
|
|
else if (quality > 80)
|
|
{
|
|
quality = 80;
|
|
}
|
|
|
|
nBitrate = nBitrate * (quality / 100.0);
|
|
|
|
return nBitrate;
|
|
}
|
|
|
|
BOOL CVideoEncoder::init(VideoEncoderParam * params)
|
|
{
|
|
memcpy(&m_EncoderParams, params, sizeof(VideoEncoderParam));
|
|
|
|
if (m_EncoderParams.DstHeight < 0)
|
|
{
|
|
m_EncoderParams.DstHeight = -m_EncoderParams.DstHeight;
|
|
}
|
|
|
|
m_EncoderParams.DstWidth = m_EncoderParams.DstWidth / 2 * 2; // align to 2
|
|
m_EncoderParams.DstHeight = m_EncoderParams.DstHeight / 2 * 2; // align to 2
|
|
|
|
m_nCodecId = to_video_avcodecid(m_EncoderParams.DstCodec);
|
|
|
|
AVCodec * pCodec = avcodec_find_encoder(m_nCodecId);
|
|
if (pCodec == NULL)
|
|
{
|
|
log_print(HT_LOG_ERR, "avcodec_find_encoder failed, %d\r\n", m_nCodecId);
|
|
return FALSE;
|
|
}
|
|
|
|
m_pCodecCtx = avcodec_alloc_context3(pCodec);
|
|
if (m_pCodecCtx == NULL)
|
|
{
|
|
log_print(HT_LOG_ERR, "avcodec_alloc_context3 failed\r\n");
|
|
return FALSE;
|
|
}
|
|
|
|
// set destination pixmel format
|
|
|
|
if (pCodec->pix_fmts)
|
|
{
|
|
m_nDstPixFmt = pCodec->pix_fmts[0];
|
|
}
|
|
else if (AV_CODEC_ID_MJPEG == m_nCodecId)
|
|
{
|
|
m_nDstPixFmt = AV_PIX_FMT_YUVJ420P;
|
|
}
|
|
else
|
|
{
|
|
m_nDstPixFmt = AV_PIX_FMT_YUV420P;
|
|
}
|
|
|
|
int num;
|
|
|
|
if (m_EncoderParams.DstFramerate - (int)m_EncoderParams.DstFramerate > 0.0)
|
|
{
|
|
num = (int)(1001 * m_EncoderParams.DstFramerate) + 1;
|
|
}
|
|
else
|
|
{
|
|
num = (int)(1001 * m_EncoderParams.DstFramerate);
|
|
}
|
|
|
|
// set video encoder parameters
|
|
m_pCodecCtx->qblur = 0.5;
|
|
m_pCodecCtx->qcompress = 0.5;
|
|
m_pCodecCtx->b_quant_offset = 1.25;
|
|
m_pCodecCtx->b_quant_factor = 1.25;
|
|
m_pCodecCtx->i_quant_offset = 0.0;
|
|
m_pCodecCtx->i_quant_factor = (float)-0.8;
|
|
|
|
m_pCodecCtx->codec_id = m_nCodecId;
|
|
m_pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
|
|
m_pCodecCtx->width = m_EncoderParams.DstWidth;
|
|
m_pCodecCtx->height = m_EncoderParams.DstHeight;
|
|
m_pCodecCtx->gop_size = m_EncoderParams.DstFramerate;
|
|
m_pCodecCtx->pix_fmt = m_nDstPixFmt;
|
|
m_pCodecCtx->time_base.num = 1001;
|
|
m_pCodecCtx->time_base.den = num;
|
|
m_pCodecCtx->framerate.num = num;
|
|
m_pCodecCtx->framerate.den = 1001;
|
|
m_pCodecCtx->qmin = 3;
|
|
m_pCodecCtx->qmax = 31;
|
|
|
|
if (m_EncoderParams.DstBitrate > 0)
|
|
{
|
|
m_pCodecCtx->bit_rate = m_EncoderParams.DstBitrate * 1000;
|
|
}
|
|
else
|
|
{
|
|
m_pCodecCtx->bit_rate = computeBitrate(m_nCodecId, m_EncoderParams.DstWidth, m_EncoderParams.DstHeight, m_EncoderParams.DstFramerate, 100);
|
|
}
|
|
|
|
if (m_nCodecId == AV_CODEC_ID_MPEG4)
|
|
{
|
|
m_pCodecCtx->profile = FF_PROFILE_MPEG4_SIMPLE;
|
|
m_pCodecCtx->codec_tag = 0x30355844;
|
|
}
|
|
else if (m_nCodecId == AV_CODEC_ID_H264)
|
|
{
|
|
m_pCodecCtx->thread_count = 1;
|
|
m_pCodecCtx->profile = FF_PROFILE_H264_MAIN;
|
|
|
|
av_opt_set(m_pCodecCtx->priv_data, "preset", "ultrafast", 0);
|
|
av_opt_set(m_pCodecCtx->priv_data, "tune", "zerolatency", 0);
|
|
}
|
|
else if (m_nCodecId == AV_CODEC_ID_HEVC)
|
|
{
|
|
m_pCodecCtx->profile = FF_PROFILE_HEVC_MAIN;
|
|
|
|
av_opt_set(m_pCodecCtx->priv_data, "preset", "ultrafast", 0);
|
|
av_opt_set(m_pCodecCtx->priv_data, "tune", "zerolatency", 0);
|
|
av_opt_set(m_pCodecCtx->priv_data, "x265-params", "log-level=-1", 0);
|
|
}
|
|
else if (m_nCodecId == AV_CODEC_ID_MJPEG)
|
|
{
|
|
m_pCodecCtx->color_range = AVCOL_RANGE_JPEG;
|
|
|
|
av_opt_set(m_pCodecCtx->priv_data, "huffman", "default", 0);
|
|
}
|
|
|
|
if (m_nCodecId != AV_CODEC_ID_MPEG4)
|
|
{
|
|
m_pCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
|
}
|
|
|
|
if (avcodec_thread_open(m_pCodecCtx, pCodec, NULL) < 0)
|
|
{
|
|
log_print(HT_LOG_ERR, "avcodec_thread_open failed, video encoder\r\n");
|
|
return FALSE;
|
|
}
|
|
|
|
m_bInited = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CVideoEncoder::uninit()
|
|
{
|
|
flush();
|
|
|
|
if (m_pCodecCtx)
|
|
{
|
|
avcodec_thread_close(m_pCodecCtx);
|
|
avcodec_free_context(&m_pCodecCtx);
|
|
}
|
|
|
|
if (m_pFrameSrc)
|
|
{
|
|
av_frame_free(&m_pFrameSrc);
|
|
}
|
|
|
|
if (m_pFrameYuv)
|
|
{
|
|
av_frame_free(&m_pFrameYuv);
|
|
}
|
|
|
|
m_bInited = FALSE;
|
|
}
|
|
|
|
void CVideoEncoder::flush()
|
|
{
|
|
if (NULL == m_pCodecCtx ||
|
|
NULL == m_pCodecCtx->codec ||
|
|
!(m_pCodecCtx->codec->capabilities | AV_CODEC_CAP_DELAY))
|
|
{
|
|
return;
|
|
}
|
|
|
|
encode(NULL);
|
|
}
|
|
|
|
BOOL CVideoEncoder::encode(uint8 * data, int size)
|
|
{
|
|
if (!m_bInited)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (NULL == m_pFrameSrc)
|
|
{
|
|
m_pFrameSrc = av_frame_alloc();
|
|
|
|
if (m_pFrameSrc)
|
|
{
|
|
m_pFrameSrc->format = m_EncoderParams.SrcPixFmt;
|
|
m_pFrameSrc->width = m_EncoderParams.SrcWidth;
|
|
m_pFrameSrc->height = m_EncoderParams.SrcHeight;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
av_image_fill_arrays(m_pFrameSrc->data, m_pFrameSrc->linesize, data, m_EncoderParams.SrcPixFmt, m_pFrameSrc->width, m_pFrameSrc->height, 1);
|
|
|
|
return encode(m_pFrameSrc);
|
|
}
|
|
|
|
BOOL CVideoEncoder::encode(AVFrame * pFrame)
|
|
{
|
|
if (!m_bInited)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
SwsContext * pSwsCtx = NULL;
|
|
|
|
if (pFrame &&
|
|
(pFrame->format != m_nDstPixFmt ||
|
|
pFrame->width != m_EncoderParams.DstWidth ||
|
|
pFrame->height != m_EncoderParams.DstHeight))
|
|
{
|
|
if (NULL == m_pFrameYuv)
|
|
{
|
|
m_pFrameYuv = av_frame_alloc();
|
|
if (m_pFrameYuv)
|
|
{
|
|
m_pFrameYuv->format = m_nDstPixFmt;
|
|
m_pFrameYuv->width = m_EncoderParams.DstWidth;
|
|
m_pFrameYuv->height = m_EncoderParams.DstHeight;
|
|
|
|
if (av_frame_get_buffer(m_pFrameYuv, 0) < 0)
|
|
{
|
|
log_print(HT_LOG_ERR, "%s, av_frame_get_buffer failed\r\n", __FUNCTION__);
|
|
return FALSE;
|
|
}
|
|
|
|
av_frame_make_writable(m_pFrameYuv); // make sure the frame data is writable
|
|
}
|
|
else
|
|
{
|
|
log_print(HT_LOG_ERR, "%s, av_frame_alloc failed\r\n", __FUNCTION__);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
pSwsCtx = sws_getContext(pFrame->width, pFrame->height, (AVPixelFormat)pFrame->format,
|
|
m_pFrameYuv->width, m_pFrameYuv->height, m_nDstPixFmt,
|
|
SWS_FAST_BILINEAR, NULL, NULL, NULL);
|
|
}
|
|
|
|
AVFrame * pFrameSrc = NULL;
|
|
|
|
if (NULL == pSwsCtx)
|
|
{
|
|
pFrameSrc = pFrame;
|
|
}
|
|
else if (pFrame)
|
|
{
|
|
if (m_pFrameYuv->height != sws_scale(pSwsCtx, pFrame->data, pFrame->linesize, 0, pFrame->height, m_pFrameYuv->data, m_pFrameYuv->linesize))
|
|
{
|
|
log_print(HT_LOG_ERR, "%s, sws_scale failed\r\n", __FUNCTION__);
|
|
return FALSE;
|
|
}
|
|
|
|
sws_freeContext(pSwsCtx);
|
|
|
|
pFrameSrc = m_pFrameYuv;
|
|
}
|
|
|
|
if (pFrameSrc)
|
|
{
|
|
if ((m_nFrameIndex % (int)m_EncoderParams.DstFramerate) == 0)
|
|
{
|
|
pFrameSrc->pict_type = AV_PICTURE_TYPE_I;
|
|
pFrameSrc->key_frame = 1;
|
|
}
|
|
else
|
|
{
|
|
pFrameSrc->pict_type = AV_PICTURE_TYPE_P;
|
|
pFrameSrc->key_frame = 0;
|
|
}
|
|
|
|
m_nFrameIndex++;
|
|
|
|
if (m_EncoderParams.Updown)
|
|
{
|
|
pFrameSrc->data[0] += pFrameSrc->linesize[0] * (m_EncoderParams.DstHeight - 1);
|
|
pFrameSrc->linesize[0] *= -1;
|
|
pFrameSrc->data[1] += pFrameSrc->linesize[1] * (m_EncoderParams.DstHeight / 2 - 1);
|
|
pFrameSrc->linesize[1] *= -1;
|
|
pFrameSrc->data[2] += pFrameSrc->linesize[2] * (m_EncoderParams.DstHeight / 2 - 1);
|
|
pFrameSrc->linesize[2] *= -1;
|
|
}
|
|
}
|
|
|
|
int ret;
|
|
AVPacket pkt;
|
|
|
|
av_init_packet(&pkt);
|
|
pkt.data = 0;
|
|
pkt.size = 0;
|
|
|
|
if (m_nCodecId == AV_CODEC_ID_MPEG4 && pFrameSrc)
|
|
{
|
|
// MP4 encoding PTS needs to keep increasing
|
|
pFrameSrc->pts = pFrameSrc->pkt_dts = m_pts;
|
|
m_pts += 2;
|
|
}
|
|
|
|
ret = avcodec_send_frame(m_pCodecCtx, pFrameSrc);
|
|
if (ret < 0)
|
|
{
|
|
log_print(HT_LOG_ERR, "%s, error sending a frame for encoding\r\n", __FUNCTION__);
|
|
return FALSE;
|
|
}
|
|
|
|
while (ret >= 0)
|
|
{
|
|
ret = avcodec_receive_packet(m_pCodecCtx, &pkt);
|
|
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
|
{
|
|
return TRUE;
|
|
}
|
|
else if (ret < 0)
|
|
{
|
|
log_print(HT_LOG_ERR, "%s, error during encoding\r\n", __FUNCTION__);
|
|
return FALSE;
|
|
}
|
|
|
|
if (pkt.data && pkt.size > 0)
|
|
{
|
|
procData(pkt.data, pkt.size);
|
|
}
|
|
else
|
|
{
|
|
log_print(HT_LOG_WARN, "%s, data is null\r\n", __FUNCTION__);
|
|
}
|
|
|
|
av_packet_unref(&pkt);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CVideoEncoder::procData(uint8 * data, int size)
|
|
{
|
|
VideoEncoderCB * p_cb = NULL;
|
|
LINKED_NODE * p_node = NULL;
|
|
|
|
sys_os_mutex_enter(m_pCallbackMutex);
|
|
|
|
p_node = h_list_lookup_start(m_pCallbackList);
|
|
while (p_node)
|
|
{
|
|
p_cb = (VideoEncoderCB *) p_node->p_data;
|
|
if (p_cb->pCallback != NULL)
|
|
{
|
|
uint8 * buff = data;
|
|
int len = size;
|
|
|
|
if (m_nCodecId == AV_CODEC_ID_H264 && m_pCodecCtx && m_pCodecCtx->extradata_size > 8)
|
|
{
|
|
uint8 naltype = avc_h264_nalu_type(data, size);
|
|
if (naltype == H264_NAL_IDR)
|
|
{
|
|
len = m_pCodecCtx->extradata_size + size;
|
|
buff = (uint8 *)malloc(len);
|
|
if (buff)
|
|
{
|
|
memcpy(buff, m_pCodecCtx->extradata, m_pCodecCtx->extradata_size);
|
|
memcpy(buff + m_pCodecCtx->extradata_size, data, size);
|
|
}
|
|
else
|
|
{
|
|
buff = data;
|
|
len = size;
|
|
}
|
|
}
|
|
}
|
|
else if (m_nCodecId == AV_CODEC_ID_HEVC && m_pCodecCtx && m_pCodecCtx->extradata_size > 8)
|
|
{
|
|
uint8 naltype = avc_h265_nalu_type(data, size);
|
|
if (naltype >= HEVC_NAL_BLA_W_LP && naltype <= HEVC_NAL_CRA_NUT)
|
|
{
|
|
len = m_pCodecCtx->extradata_size + size;
|
|
buff = (uint8 *)malloc(len);
|
|
if (buff)
|
|
{
|
|
memcpy(buff, m_pCodecCtx->extradata, m_pCodecCtx->extradata_size);
|
|
memcpy(buff + m_pCodecCtx->extradata_size, data, size);
|
|
}
|
|
else
|
|
{
|
|
buff = data;
|
|
len = size;
|
|
}
|
|
}
|
|
}
|
|
|
|
p_cb->pCallback(buff, len, 1, p_cb->pUserdata);
|
|
|
|
if (buff != data)
|
|
{
|
|
free(buff);
|
|
}
|
|
}
|
|
|
|
p_node = h_list_lookup_next(m_pCallbackList, p_node);
|
|
}
|
|
h_list_lookup_end(m_pCallbackList);
|
|
|
|
sys_os_mutex_leave(m_pCallbackMutex);
|
|
}
|
|
|
|
BOOL CVideoEncoder::isCallbackExist(VideoDataCallback pCallback, void *pUserdata)
|
|
{
|
|
BOOL exist = FALSE;
|
|
VideoEncoderCB * p_cb = NULL;
|
|
LINKED_NODE * p_node = NULL;
|
|
|
|
sys_os_mutex_enter(m_pCallbackMutex);
|
|
|
|
p_node = h_list_lookup_start(m_pCallbackList);
|
|
while (p_node)
|
|
{
|
|
p_cb = (VideoEncoderCB *) p_node->p_data;
|
|
if (p_cb->pCallback == pCallback && p_cb->pUserdata == pUserdata)
|
|
{
|
|
exist = TRUE;
|
|
break;
|
|
}
|
|
|
|
p_node = h_list_lookup_next(m_pCallbackList, p_node);
|
|
}
|
|
h_list_lookup_end(m_pCallbackList);
|
|
|
|
sys_os_mutex_leave(m_pCallbackMutex);
|
|
|
|
return exist;
|
|
}
|
|
|
|
void CVideoEncoder::addCallback(VideoDataCallback pCallback, void *pUserdata)
|
|
{
|
|
if (isCallbackExist(pCallback, pUserdata))
|
|
{
|
|
return;
|
|
}
|
|
|
|
VideoEncoderCB * p_cb = (VideoEncoderCB *) malloc(sizeof(VideoEncoderCB));
|
|
if (NULL == p_cb)
|
|
{
|
|
return;
|
|
}
|
|
|
|
p_cb->pCallback = pCallback;
|
|
p_cb->pUserdata = pUserdata;
|
|
p_cb->bFirst = TRUE;
|
|
|
|
sys_os_mutex_enter(m_pCallbackMutex);
|
|
h_list_add_at_back(m_pCallbackList, p_cb);
|
|
sys_os_mutex_leave(m_pCallbackMutex);
|
|
}
|
|
|
|
void CVideoEncoder::delCallback(VideoDataCallback pCallback, void *pUserdata)
|
|
{
|
|
VideoEncoderCB * p_cb = NULL;
|
|
LINKED_NODE * p_node = NULL;
|
|
|
|
sys_os_mutex_enter(m_pCallbackMutex);
|
|
|
|
p_node = h_list_lookup_start(m_pCallbackList);
|
|
while (p_node)
|
|
{
|
|
p_cb = (VideoEncoderCB *) p_node->p_data;
|
|
if (p_cb->pCallback == pCallback && p_cb->pUserdata == pUserdata)
|
|
{
|
|
free(p_cb);
|
|
|
|
h_list_remove(m_pCallbackList, p_node);
|
|
break;
|
|
}
|
|
|
|
p_node = h_list_lookup_next(m_pCallbackList, p_node);
|
|
}
|
|
h_list_lookup_end(m_pCallbackList);
|
|
|
|
sys_os_mutex_leave(m_pCallbackMutex);
|
|
}
|
|
|
|
char * CVideoEncoder::getH264AuxSDPLine(int rtp_pt)
|
|
{
|
|
uint8 * sps = NULL; uint32 spsSize = 0;
|
|
uint8 * pps = NULL; uint32 ppsSize = 0;
|
|
|
|
if (NULL == m_pCodecCtx || m_pCodecCtx->extradata_size <= 8)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
uint8 *r, *end = m_pCodecCtx->extradata + m_pCodecCtx->extradata_size;
|
|
r = avc_find_startcode(m_pCodecCtx->extradata, end);
|
|
|
|
while (r < end)
|
|
{
|
|
uint8 *r1;
|
|
|
|
while (!*(r++));
|
|
r1 = avc_find_startcode(r, end);
|
|
|
|
int nal_type = (r[0] & 0x1F);
|
|
|
|
if (H264_NAL_PPS == nal_type)
|
|
{
|
|
pps = r;
|
|
ppsSize = r1 - r;
|
|
}
|
|
else if (H264_NAL_SPS == nal_type)
|
|
{
|
|
sps = r;
|
|
spsSize = r1 - r;
|
|
}
|
|
|
|
r = r1;
|
|
}
|
|
|
|
if (NULL == sps || spsSize == 0 ||
|
|
NULL == pps || ppsSize == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// Set up the "a=fmtp:" SDP line for this stream:
|
|
uint8* spsWEB = new uint8[spsSize]; // "WEB" means "Without Emulation Bytes"
|
|
uint32 spsWEBSize = remove_emulation_bytes(spsWEB, spsSize, sps, spsSize);
|
|
if (spsWEBSize < 4)
|
|
{
|
|
// Bad SPS size => assume our source isn't ready
|
|
delete[] spsWEB;
|
|
return NULL;
|
|
}
|
|
uint32 profileLevelId = (spsWEB[1]<<16) | (spsWEB[2]<<8) | spsWEB[3];
|
|
delete[] spsWEB;
|
|
|
|
char* sps_base64 = new char[spsSize*2+1];
|
|
char* pps_base64 = new char[ppsSize*2+1];
|
|
|
|
base64_encode(sps, spsSize, sps_base64, spsSize*2+1);
|
|
base64_encode(pps, ppsSize, pps_base64, ppsSize*2+1);
|
|
|
|
char const* fmtpFmt =
|
|
"a=fmtp:%d packetization-mode=1"
|
|
";profile-level-id=%06X"
|
|
";sprop-parameter-sets=%s,%s";
|
|
|
|
uint32 fmtpFmtSize = strlen(fmtpFmt)
|
|
+ 3 /* max char len */
|
|
+ 6 /* 3 bytes in hex */
|
|
+ strlen(sps_base64) + strlen(pps_base64);
|
|
|
|
char* fmtp = new char[fmtpFmtSize+1];
|
|
memset(fmtp, 0, fmtpFmtSize+1);
|
|
|
|
sprintf(fmtp, fmtpFmt, rtp_pt, profileLevelId, sps_base64, pps_base64);
|
|
|
|
delete[] sps_base64;
|
|
delete[] pps_base64;
|
|
|
|
return fmtp;
|
|
}
|
|
|
|
char * CVideoEncoder::getH265AuxSDPLine(int rtp_pt)
|
|
{
|
|
uint8* vps = NULL; uint32 vpsSize = 0;
|
|
uint8* sps = NULL; uint32 spsSize = 0;
|
|
uint8* pps = NULL; uint32 ppsSize = 0;
|
|
|
|
if (NULL == m_pCodecCtx || m_pCodecCtx->extradata_size < 12)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
uint8 *r, *end = m_pCodecCtx->extradata + m_pCodecCtx->extradata_size;
|
|
r = avc_find_startcode(m_pCodecCtx->extradata, end);
|
|
|
|
while (r < end)
|
|
{
|
|
uint8 *r1;
|
|
|
|
while (!*(r++));
|
|
r1 = avc_find_startcode(r, end);
|
|
|
|
int nal_type = (r[0] >> 1) & 0x3F;
|
|
|
|
if (HEVC_NAL_VPS == nal_type)
|
|
{
|
|
vps = r;
|
|
vpsSize = r1 - r;
|
|
}
|
|
else if (HEVC_NAL_PPS == nal_type)
|
|
{
|
|
pps = r;
|
|
ppsSize = r1 - r;
|
|
}
|
|
else if (HEVC_NAL_SPS == nal_type)
|
|
{
|
|
sps = r;
|
|
spsSize = r1 - r;
|
|
}
|
|
|
|
r = r1;
|
|
}
|
|
|
|
if (NULL == vps || vpsSize == 0 ||
|
|
NULL == sps || spsSize == 0 ||
|
|
NULL == pps || ppsSize == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// Set up the "a=fmtp:" SDP line for this stream.
|
|
uint8* vpsWEB = new uint8[vpsSize]; // "WEB" means "Without Emulation Bytes"
|
|
uint32 vpsWEBSize = remove_emulation_bytes(vpsWEB, vpsSize, vps, vpsSize);
|
|
if (vpsWEBSize < 6/*'profile_tier_level' offset*/ + 12/*num 'profile_tier_level' bytes*/)
|
|
{
|
|
// Bad VPS size => assume our source isn't ready
|
|
delete[] vpsWEB;
|
|
return NULL;
|
|
}
|
|
|
|
uint8 const* profileTierLevelHeaderBytes = &vpsWEB[6];
|
|
uint32 profileSpace = profileTierLevelHeaderBytes[0]>>6; // general_profile_space
|
|
uint32 profileId = profileTierLevelHeaderBytes[0]&0x1F; // general_profile_idc
|
|
uint32 tierFlag = (profileTierLevelHeaderBytes[0]>>5)&0x1;// general_tier_flag
|
|
uint32 levelId = profileTierLevelHeaderBytes[11]; // general_level_idc
|
|
uint8 const* interop_constraints = &profileTierLevelHeaderBytes[5];
|
|
char interopConstraintsStr[100];
|
|
sprintf(interopConstraintsStr, "%02X%02X%02X%02X%02X%02X",
|
|
interop_constraints[0], interop_constraints[1], interop_constraints[2],
|
|
interop_constraints[3], interop_constraints[4], interop_constraints[5]);
|
|
delete[] vpsWEB;
|
|
|
|
char* sprop_vps = new char[vpsSize*2+1];
|
|
char* sprop_sps = new char[spsSize*2+1];
|
|
char* sprop_pps = new char[ppsSize*2+1];
|
|
|
|
base64_encode(vps, vpsSize, sprop_vps, vpsSize*2+1);
|
|
base64_encode(sps, spsSize, sprop_sps, spsSize*2+1);
|
|
base64_encode(pps, ppsSize, sprop_pps, ppsSize*2+1);
|
|
|
|
char const* fmtpFmt =
|
|
"a=fmtp:%d profile-space=%u"
|
|
";profile-id=%u"
|
|
";tier-flag=%u"
|
|
";level-id=%u"
|
|
";interop-constraints=%s"
|
|
";sprop-vps=%s"
|
|
";sprop-sps=%s"
|
|
";sprop-pps=%s";
|
|
|
|
uint32 fmtpFmtSize = strlen(fmtpFmt)
|
|
+ 3 /* max num chars: rtpPayloadType */ + 20 /* max num chars: profile_space */
|
|
+ 20 /* max num chars: profile_id */
|
|
+ 20 /* max num chars: tier_flag */
|
|
+ 20 /* max num chars: level_id */
|
|
+ strlen(interopConstraintsStr)
|
|
+ strlen(sprop_vps)
|
|
+ strlen(sprop_sps)
|
|
+ strlen(sprop_pps);
|
|
|
|
char* fmtp = new char[fmtpFmtSize+1];
|
|
memset(fmtp, 0, fmtpFmtSize+1);
|
|
|
|
sprintf(fmtp, fmtpFmt,
|
|
rtp_pt, profileSpace,
|
|
profileId,
|
|
tierFlag,
|
|
levelId,
|
|
interopConstraintsStr,
|
|
sprop_vps,
|
|
sprop_sps,
|
|
sprop_pps);
|
|
|
|
delete[] sprop_vps;
|
|
delete[] sprop_sps;
|
|
delete[] sprop_pps;
|
|
|
|
return fmtp;
|
|
}
|
|
|
|
char * CVideoEncoder::getMP4AuxSDPLine(int rtp_pt)
|
|
{
|
|
if (NULL == m_pCodecCtx || m_pCodecCtx->extradata_size == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
char const* fmtpFmt =
|
|
"a=fmtp:%d "
|
|
"profile-level-id=%d;"
|
|
"config=";
|
|
uint32 fmtpFmtSize = strlen(fmtpFmt)
|
|
+ 3 /* max char len */
|
|
+ 3 /* max char len */
|
|
+ 2*m_pCodecCtx->extradata_size; /* 2*, because each byte prints as 2 chars */
|
|
|
|
char* fmtp = new char[fmtpFmtSize+1];
|
|
memset(fmtp, 0, fmtpFmtSize+1);
|
|
|
|
sprintf(fmtp, fmtpFmt, rtp_pt, 1);
|
|
char* endPtr = &fmtp[strlen(fmtp)];
|
|
for (int i = 0; i < m_pCodecCtx->extradata_size; ++i)
|
|
{
|
|
sprintf(endPtr, "%02X", m_pCodecCtx->extradata[i]);
|
|
endPtr += 2;
|
|
}
|
|
|
|
return fmtp;
|
|
}
|
|
|
|
char * CVideoEncoder::getAuxSDPLine(int rtp_pt)
|
|
{
|
|
if (m_nCodecId == AV_CODEC_ID_H264)
|
|
{
|
|
return getH264AuxSDPLine(rtp_pt);
|
|
}
|
|
else if (m_nCodecId == AV_CODEC_ID_MPEG4)
|
|
{
|
|
return getMP4AuxSDPLine(rtp_pt);
|
|
}
|
|
else if (m_nCodecId == AV_CODEC_ID_HEVC)
|
|
{
|
|
return getH265AuxSDPLine(rtp_pt);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
BOOL CVideoEncoder::getExtraData(uint8 ** extradata, int * extralen)
|
|
{
|
|
if (m_pCodecCtx && m_pCodecCtx->extradata_size > 0)
|
|
{
|
|
*extradata = m_pCodecCtx->extradata;
|
|
*extralen = m_pCodecCtx->extradata_size;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
|