Files
gb28181/GB28181Device/android/video_encoder_android.cpp
2024-12-15 20:42:32 +08:00

1083 lines
32 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_android.h"
#include "media_format.h"
#include "media_util.h"
#include "h264.h"
#include "h265.h"
#include "base64.h"
#include <QAndroidJniObject>
#include <QAndroidJniEnvironment>
static void swapYV12toYUV420P(const uint8_t * input, uint8_t * output, int width, int height, int padding)
{
memcpy(output, input, width*height);
memcpy(output+width*height+padding, input+width*height+width*height/4, width*height/4);
memcpy(output+width*height+width*height/4+padding, input+width*height, width*height/4);
}
static void swapYV12toYUV420SP(const uint8_t * input, uint8_t * output, int width, int height, int padding)
{
int frameSize = width * height;
int qFrameSize = frameSize / 4;
memcpy(output, input, frameSize);
for (int i = 0; i < qFrameSize; i++)
{
output[frameSize + i * 2 + padding] = input[frameSize + qFrameSize + i];
output[frameSize + i * 2 + 1 + padding] = input[frameSize + i];
}
}
static void swapNV21toYUV420P(const uint8_t * input, uint8_t * output, int width, int height, int padding)
{
int frameSize = width * height;
int qFrameSize = frameSize / 4;
memcpy(output, input, frameSize);
for (int i = 0; i < qFrameSize; i++)
{
output[frameSize + qFrameSize + i + padding] = input[frameSize + i * 2];
output[frameSize + i + padding] = input[frameSize + i * 2 + 1];
}
}
static void swapNV21toYUV420SP(const uint8_t * input, uint8_t * output, int width, int height, int padding)
{
int frameSize = width * height;
int qFrameSize = frameSize / 4;
memcpy(output, input, frameSize);
for (int i = 0; i < qFrameSize; i++)
{
output[frameSize + i * 2 + padding] = input[frameSize + i * 2 + 1];
output[frameSize + i * 2 + 1 + padding] = input[frameSize + i * 2];
}
}
static void swapYUV420PtoYUV420SP(const uint8_t * input, uint8_t * output, int width, int height, int padding)
{
int frameSize = width * height;
int qFrameSize = frameSize / 4;
memcpy(output, input, frameSize);
for (int i = 0; i < qFrameSize; i++)
{
output[frameSize + i * 2 + 1 + padding] = input[frameSize + qFrameSize + i];
output[frameSize + i * 2 + padding] = input[frameSize + i];
}
}
static BOOL IsSupportedColorFormat(int color_format)
{
static const int supported_colorformats[] =
{
19, // COLOR_FormatYUV420Planar
21, // COLOR_FormatYUV420SemiPlanar
-1
};
for (const int *ptr = supported_colorformats; *ptr != -1; ptr++)
{
if (color_format == *ptr)
{
return TRUE;
}
}
return FALSE;
}
static BOOL SupportColorFormat(std::vector<int> color_formats, int format)
{
for (size_t k = 0; k < color_formats.size(); ++k)
{
if (color_formats[k] == format)
{
return TRUE;
}
}
return FALSE;
}
static BOOL IsBlacklisted(const std::string &name)
{
static const char *blacklisted_encoders[] =
{
// No software encoders
"OMX.google",
NULL
};
for (const char **ptr = blacklisted_encoders; *ptr; ptr++)
{
if (!strncasecmp(*ptr, name.c_str(), strlen(*ptr)))
{
return TRUE;
}
}
return FALSE;
}
CAndroidVideoEncoder::CAndroidVideoEncoder()
{
m_dstFmt = 0;
m_bInited = FALSE;
m_yuv = NULL;
m_orgyuv = NULL;
m_mime = "video/avc";
m_pExtra = NULL;
m_nExtraLen = 0;
m_nInputTimeout = 0;
m_nOutputTimeout = 0;
m_pCallbackMutex = sys_os_create_mutex();
m_pCallbackList = h_list_create(FALSE);
}
CAndroidVideoEncoder::~CAndroidVideoEncoder()
{
uninit();
h_list_free_container(m_pCallbackList);
sys_os_destroy_sig_mutex(m_pCallbackMutex);
}
BOOL CAndroidVideoEncoder::init(VideoEncoderParam * params)
{
QAndroidJniEnvironment env;
if (QAndroidJniObject::getStaticField<jint>("android/os/Build$VERSION", "SDK_INT") < 17)
{
log_print(HT_LOG_ERR, "%s, sdk version too old!!!\r\n", __FUNCTION__);
return FALSE;
}
if (VIDEO_CODEC_H264 == params->DstCodec)
{
m_mime = "video/avc";
}
else if (VIDEO_CODEC_H265 == params->DstCodec)
{
m_mime = "video/hevc";
}
else
{
log_print(HT_LOG_ERR, "%s, unsupport codec %d\r\n", __FUNCTION__, params->DstCodec);
return FALSE;
}
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
// mediacodec crashes with null size. Trap this...
if (!m_EncoderParams.DstWidth || !m_EncoderParams.DstHeight)
{
log_print(HT_LOG_ERR, "%s, null size, cannot handle\r\n", __FUNCTION__);
return FALSE;
}
if (m_EncoderParams.SrcPixFmt != VIDEO_FMT_NV21 &&
m_EncoderParams.SrcPixFmt != VIDEO_FMT_YV12 &&
m_EncoderParams.SrcPixFmt != VIDEO_FMT_YUV420P)
{
log_print(HT_LOG_ERR, "%s, srcfmt = %d, unsupport src format\r\n", __FUNCTION__, m_EncoderParams.SrcPixFmt);
return FALSE;
}
int num_codecs = QAndroidJniObject::callStaticMethod<jint>("android/media/MediaCodecList", "getCodecCount", "()I");
log_print(HT_LOG_INFO, "%s, num_codecs = %d\r\n", __FUNCTION__, num_codecs);
for (int i = 0; i < num_codecs; i++)
{
QAndroidJniObject codecInfo = QAndroidJniObject::callStaticObjectMethod("android/media/MediaCodecList",
"getCodecInfoAt", "(I)Landroid/media/MediaCodecInfo;", i);
if (!codecInfo.callMethod<jboolean>("isEncoder", "()Z"))
{
continue;
}
QAndroidJniObject codecInfoName = codecInfo.callObjectMethod("getName", "()Ljava/lang/String;");
m_codecname = codecInfoName.toString().toStdString();
log_print(HT_LOG_INFO, "%s, codecname = %s\r\n", __FUNCTION__, m_codecname.c_str());
if (IsBlacklisted(m_codecname))
{
continue;
}
QString mime = QString::fromStdString(m_mime);
QAndroidJniObject mimeJava = QAndroidJniObject::fromString(mime);
QAndroidJniObject codecCaps = codecInfo.callObjectMethod("getCapabilitiesForType",
"(Ljava/lang/String;)Landroid/media/MediaCodecInfo$CodecCapabilities;",
mimeJava.object<jstring>());
if (env->ExceptionCheck())
{
// Unsupported type?
env->ExceptionClear();
continue;
}
QAndroidJniObject colorFormats = codecCaps.getObjectField("colorFormats", "[I");
jsize size = env->GetArrayLength(colorFormats.object<jintArray>());
std::vector<int> color_formats;
color_formats.resize(size);
env->GetIntArrayRegion(colorFormats.object<jintArray>(), 0, size, (jint*)color_formats.data());
for (size_t k = 0; k < color_formats.size(); ++k)
{
log_print(HT_LOG_DBG, "%s, colorFormat = %d\r\n", __FUNCTION__, color_formats[k]);
}
QAndroidJniObject supportedTypes = codecInfo.callObjectMethod("getSupportedTypes", "()[Ljava/lang/String;");
size = env->GetArrayLength(supportedTypes.object<jobjectArray>());
for (int j = 0; j < size; ++j)
{
jobject typeJava = env->GetObjectArrayElement(supportedTypes.object<jobjectArray>(), j);
QAndroidJniObject type = QAndroidJniObject::fromLocalRef(typeJava);
QString typeStr = type.toString();
log_print(HT_LOG_INFO, "%s, type = %s\r\n", __FUNCTION__, typeStr.toStdString().c_str());
if (typeStr.toStdString() == m_mime)
{
QString codecName = QString::fromStdString(m_codecname);
QAndroidJniObject codecNameJava = QAndroidJniObject::fromString(codecName);
m_codec = QAndroidJniObject::callStaticObjectMethod("android/media/MediaCodec",
"createByCodecName", "(Ljava/lang/String;)Landroid/media/MediaCodec;",
codecNameJava.object<jstring>());
if (env->ExceptionCheck())
{
log_print(HT_LOG_ERR, "%s, create mediacodec failed\r\n", __FUNCTION__);
env->ExceptionClear();
continue;
}
if (VIDEO_FMT_NV21 == m_EncoderParams.SrcPixFmt)
{
int format = QAndroidJniObject::getStaticField<jint>("android/media/MediaCodecInfo$CodecCapabilities",
"COLOR_FormatYUV420SemiPlanar");
if (SupportColorFormat(color_formats, format))
{
m_dstFmt = format;
}
}
else if (VIDEO_FMT_YV12 == m_EncoderParams.SrcPixFmt)
{
int format = QAndroidJniObject::getStaticField<jint>("android/media/MediaCodecInfo$CodecCapabilities",
"COLOR_FormatYUV420Planar");
if (SupportColorFormat(color_formats, format))
{
m_dstFmt = format;
}
}
else
{
int format = QAndroidJniObject::getStaticField<jint>("android/media/MediaCodecInfo$CodecCapabilities",
"COLOR_FormatYUV420SemiPlanar");
if (SupportColorFormat(color_formats, format))
{
m_dstFmt = format;
}
}
if (m_dstFmt == 0)
{
for (size_t k = 0; k < color_formats.size(); ++k)
{
log_print(HT_LOG_DBG, "%s, colorFormat = %d\r\n", __FUNCTION__, color_formats[k]);
if (IsSupportedColorFormat(color_formats[k]))
{
m_dstFmt = color_formats[k]; // Save color format for initial output configuration
break;
}
}
}
break;
}
}
if (m_codec.isValid())
{
break;
}
}
if (!m_codec.isValid())
{
log_print(HT_LOG_ERR, "%s, Failed to create Android MediaCodec\r\n", __FUNCTION__);
return FALSE;
}
log_print(HT_LOG_INFO, "initEncode dstFmt=%d, codecname=%s\r\n", m_dstFmt, m_codecname.c_str());
uint32 bitrate;
if (m_EncoderParams.DstBitrate > 0)
{
bitrate = m_EncoderParams.DstBitrate * 1000;
}
else
{
bitrate = m_EncoderParams.DstWidth * m_EncoderParams.DstHeight * m_EncoderParams.DstFramerate * 0.16;
}
QString mime = QString::fromStdString(m_mime);
QAndroidJniObject mimeJava = QAndroidJniObject::fromString(mime);
QAndroidJniObject mediaFormat = QAndroidJniObject::callStaticObjectMethod("android/media/MediaFormat",
"createVideoFormat",
"(Ljava/lang/String;II)Landroid/media/MediaFormat;",
mimeJava.object<jstring>(), m_EncoderParams.DstWidth, m_EncoderParams.DstHeight);
QAndroidJniObject KEY_BIT_RATE = QAndroidJniObject::getStaticObjectField("android/media/MediaFormat",
"KEY_BIT_RATE", "Ljava/lang/String;");
mediaFormat.callMethod<void>("setInteger", "(Ljava/lang/String;I)V", KEY_BIT_RATE.object<jstring>(), bitrate);
QAndroidJniObject KEY_FRAME_RATE = QAndroidJniObject::getStaticObjectField("android/media/MediaFormat",
"KEY_FRAME_RATE", "Ljava/lang/String;");
mediaFormat.callMethod<void>("setInteger", "(Ljava/lang/String;I)V", KEY_FRAME_RATE.object<jstring>(), (int)m_EncoderParams.DstFramerate);
QAndroidJniObject KEY_COLOR_FORMAT = QAndroidJniObject::getStaticObjectField("android/media/MediaFormat",
"KEY_COLOR_FORMAT", "Ljava/lang/String;");
mediaFormat.callMethod<void>("setInteger", "(Ljava/lang/String;I)V", KEY_COLOR_FORMAT.object<jstring>(), m_dstFmt);
QAndroidJniObject KEY_I_FRAME_INTERVAL = QAndroidJniObject::getStaticObjectField("android/media/MediaFormat",
"KEY_I_FRAME_INTERVAL", "Ljava/lang/String;");
mediaFormat.callMethod<void>("setInteger", "(Ljava/lang/String;I)V", KEY_I_FRAME_INTERVAL.object<jstring>(), 1);
if (VIDEO_CODEC_H264 == m_EncoderParams.DstCodec)
{
int profile = QAndroidJniObject::getStaticField<jint>("android/media/MediaCodecInfo$CodecProfileLevel",
"AVCProfileBaseline");
mediaFormat.callMethod<void>("setInteger", "(Ljava/lang/String;I)V",
QAndroidJniObject::fromString("profile").object<jstring>(), profile);
int level = QAndroidJniObject::getStaticField<jint>("android/media/MediaCodecInfo$CodecProfileLevel",
"AVCLevel31");
mediaFormat.callMethod<void>("setInteger", "(Ljava/lang/String;I)V",
QAndroidJniObject::fromString("level").object<jstring>(), level);
}
else if (VIDEO_CODEC_H265 == m_EncoderParams.DstCodec)
{
int profile = QAndroidJniObject::getStaticField<jint>("android/media/MediaCodecInfo$CodecProfileLevel",
"HEVCProfileMain");
mediaFormat.callMethod<void>("setInteger", "(Ljava/lang/String;I)V",
QAndroidJniObject::fromString("profile").object<jstring>(), profile);
int level = QAndroidJniObject::getStaticField<jint>("android/media/MediaCodecInfo$CodecProfileLevel",
"HEVCHighTierLevel4");
mediaFormat.callMethod<void>("setInteger", "(Ljava/lang/String;I)V",
QAndroidJniObject::fromString("level").object<jstring>(), level);
}
m_codec.callMethod<void>("configure", "(Landroid/media/MediaFormat;Landroid/view/Surface;Landroid/media/MediaCrypto;I)V",
mediaFormat.object<jobject>(), NULL, NULL,
QAndroidJniObject::getStaticField<jint>("android/media/MediaCodec", "CONFIGURE_FLAG_ENCODE"));
// always, check/clear jni exceptions.
if (env->ExceptionCheck())
{
log_print(HT_LOG_ERR, "%s, MediaCodec configure ExceptionCheck\r\n", __FUNCTION__);
env->ExceptionDescribe();
env->ExceptionClear();
return FALSE;
}
m_codec.callMethod<void>("start", "()V");
// always, check/clear jni exceptions.
if (env->ExceptionCheck())
{
log_print(HT_LOG_ERR, "%s, MediaCodec start ExceptionCheck\r\n", __FUNCTION__);
env->ExceptionDescribe();
env->ExceptionClear();
return FALSE;
}
m_orgyuv = new uint8[m_EncoderParams.DstWidth * m_EncoderParams.DstHeight * 3 / 2 + 2048];
m_yuv = m_orgyuv + (32 - ((int)m_orgyuv % 32));
m_bInited = TRUE;
return TRUE;
}
void CAndroidVideoEncoder::uninit()
{
QAndroidJniEnvironment env;
if (m_codec.isValid())
{
m_codec.callMethod<void>("stop", "()V");
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
m_codec.callMethod<void>("release", "()V");
if (env->ExceptionCheck())
{
env->ExceptionClear();
}
}
if (m_orgyuv)
{
delete[] m_orgyuv;
m_orgyuv = NULL;
}
if (m_pExtra)
{
free(m_pExtra);
m_pExtra = NULL;
}
m_nExtraLen = 0;
m_bInited = FALSE;
}
BOOL CAndroidVideoEncoder::encode(const uint8 * data, int size)
{
BOOL ret = FALSE;
int padding = 0;
int64 timeout_us = 5000;
int inputBufferIndex = 0;
int buffsize =0;
int dstsize = 0;
int sizeTMP = 0;
int formatYUV420Planar;
int formatYUV420SemiPlanar;
QAndroidJniEnvironment env;
if (!m_bInited)
{
return FALSE;
}
if (NULL == data || 0 == size)
{
goto FLUSH_END;
}
formatYUV420Planar = QAndroidJniObject::getStaticField<jint>("android/media/MediaCodecInfo$CodecCapabilities",
"COLOR_FormatYUV420Planar");
formatYUV420SemiPlanar = QAndroidJniObject::getStaticField<jint>("android/media/MediaCodecInfo$CodecCapabilities",
"COLOR_FormatYUV420SemiPlanar");
if (VIDEO_FMT_YV12 == m_EncoderParams.SrcPixFmt) // YV12
{
if (formatYUV420Planar == m_dstFmt)
{
swapYV12toYUV420P(data, m_yuv, m_EncoderParams.DstWidth, m_EncoderParams.DstHeight, padding);
}
else if (formatYUV420SemiPlanar == m_dstFmt)
{
swapYV12toYUV420SP(data, m_yuv, m_EncoderParams.DstWidth, m_EncoderParams.DstHeight, padding);
}
}
else if (VIDEO_FMT_NV21 == m_EncoderParams.SrcPixFmt) // NV21
{
if (formatYUV420Planar == m_dstFmt)
{
swapNV21toYUV420P(data, m_yuv, m_EncoderParams.DstWidth, m_EncoderParams.DstHeight, padding);
}
else if (formatYUV420SemiPlanar == m_dstFmt)
{
swapNV21toYUV420SP(data, m_yuv, m_EncoderParams.DstWidth, m_EncoderParams.DstHeight, padding);
}
}
else if (VIDEO_FMT_YUV420P == m_EncoderParams.SrcPixFmt)
{
if (formatYUV420Planar == m_dstFmt)
{
// so good, nothing to do.
m_yuv = (uint8 *)data;
}
else if (formatYUV420SemiPlanar == m_dstFmt)
{
swapYUV420PtoYUV420SP(data, m_yuv, m_EncoderParams.DstWidth, m_EncoderParams.DstHeight, padding);
}
}
else
{
log_print(HT_LOG_ERR, "unsupport src format (%d)\r\n", m_EncoderParams.SrcPixFmt);
return FALSE;
}
inputBufferIndex = m_codec.callMethod<jint>("dequeueInputBuffer", "(J)I", timeout_us);
if (env->ExceptionCheck())
{
log_print(HT_LOG_ERR, "%s, MediaCodec dequeueInputBuffer ExceptionCheck\r\n", __FUNCTION__);
env->ExceptionClear();
goto FLUSH_END;
}
if (inputBufferIndex >= 0)
{
QAndroidJniObject inputBuffer = m_codec.callObjectMethod("getInputBuffer", "(I)Ljava/nio/ByteBuffer;", inputBufferIndex);
buffsize = m_EncoderParams.DstWidth * m_EncoderParams.DstHeight * 3 / 2 + padding;
dstsize = inputBuffer.callMethod<jint>("capacity", "()I");
sizeTMP = buffsize > dstsize ? dstsize : buffsize;
// fetch a pointer to the ByteBuffer backing store
uint8 *dst_ptr = (uint8_t*)env->GetDirectBufferAddress(inputBuffer.object<jobject>());
if (dst_ptr)
{
memcpy(dst_ptr, m_yuv, sizeTMP);
}
else
{
log_print(HT_LOG_DBG, "%s, dst_ptr null\r\n", __FUNCTION__);
}
m_codec.callMethod<void>("queueInputBuffer", "(IIIJI)V", inputBufferIndex, 0, sizeTMP, sys_os_get_ms()*1000, 0);
if (env->ExceptionCheck())
{
log_print(HT_LOG_ERR, "%s, MediaCodec queueInputBuffer ExceptionCheck\r\n", __FUNCTION__);
env->ExceptionClear();
}
else
{
ret = TRUE;
}
m_nInputTimeout = 0;
}
else if (inputBufferIndex == QAndroidJniObject::getStaticField<jint>("android/media/MediaCodec", "INFO_TRY_AGAIN_LATER"))
{
m_nInputTimeout++;
log_print(HT_LOG_INFO, "%s, INPUT INFO_TRY_AGAIN_LATER, %d\r\n", __FUNCTION__, m_nInputTimeout);
}
else
{
log_print(HT_LOG_DBG, "%s, inputBufferIndex=%d\r\n", __FUNCTION__, inputBufferIndex);
}
FLUSH_END:
QAndroidJniObject bufferInfo("android/media/MediaCodec$BufferInfo");
int outputBufferIndex = m_codec.callMethod<jint>("dequeueOutputBuffer",
"(Landroid/media/MediaCodec$BufferInfo;J)I",
bufferInfo.object<jobject>(), timeout_us);
if (env->ExceptionCheck())
{
log_print(HT_LOG_ERR, "%s, MediaCodec dequeueOutputBuffer ExceptionCheck\r\n", __FUNCTION__);
env->ExceptionClear();
return FALSE;
}
if (outputBufferIndex >= 0)
{
QAndroidJniObject outputBuffer = m_codec.callObjectMethod("getOutputBuffer", "(I)Ljava/nio/ByteBuffer;", outputBufferIndex);
uint8 *dst_ptr = (uint8_t*)env->GetDirectBufferAddress(outputBuffer.object<jobject>());
if (dst_ptr)
{
dst_ptr += bufferInfo.getField<jint>("offset");
int buff_size = bufferInfo.getField<jint>("size");
int key_frame = 0;
uint8 nalu_t;
int extra = 0;
if (VIDEO_CODEC_H264 == m_EncoderParams.DstCodec)
{
nalu_t = avc_h264_nalu_type(dst_ptr, buff_size);
key_frame = (nalu_t == 5 ? 1 : 0);
if (nalu_t == H264_NAL_SPS || nalu_t == H264_NAL_PPS)
{
extra = 1;
}
}
else if (VIDEO_CODEC_H265 == m_EncoderParams.DstCodec)
{
nalu_t = avc_h265_nalu_type(dst_ptr, buff_size);
key_frame = (nalu_t >= 16 && nalu_t <= 21);
if (nalu_t == HEVC_NAL_VPS || nalu_t == HEVC_NAL_SPS || nalu_t == HEVC_NAL_PPS)
{
extra = 1;
}
}
if (extra && !m_pExtra)
{
if (buff_size > 0)
{
m_pExtra = (uint8 *)malloc(buff_size);
if (m_pExtra)
{
m_nExtraLen = buff_size;
memcpy(m_pExtra, dst_ptr, buff_size);
}
}
}
else
{
if (key_frame && m_nExtraLen > 0 && m_pExtra)
{
int len = buff_size + m_nExtraLen;
uint8 * buff = (uint8 *)malloc(len);
if (buff)
{
memcpy(buff, m_pExtra, m_nExtraLen);
memcpy(buff+m_nExtraLen, dst_ptr, buff_size);
procData(buff, len);
free(buff);
}
else
{
procData(m_pExtra, m_nExtraLen);
procData(dst_ptr, buff_size);
}
}
else
{
procData(dst_ptr, buff_size);
}
}
}
else
{
log_print(HT_LOG_DBG, "%s, dst_ptr null\r\n", __FUNCTION__);
}
m_codec.callMethod<void>("releaseOutputBuffer", "(IZ)V", outputBufferIndex, false);
if (env->ExceptionCheck())
{
log_print(HT_LOG_ERR, "%s, ReleaseOutputBuffer ExceptionCheck\r\n", __FUNCTION__);
env->ExceptionClear();
}
m_nOutputTimeout = 0;
}
else if (outputBufferIndex == QAndroidJniObject::getStaticField<jint>("android/media/MediaCodec", "INFO_OUTPUT_BUFFERS_CHANGED"))
{
log_print(HT_LOG_INFO, "%s, INFO_OUTPUT_BUFFERS_CHANGED\r\n", __FUNCTION__);
}
else if (outputBufferIndex == QAndroidJniObject::getStaticField<jint>("android/media/MediaCodec", "INFO_OUTPUT_FORMAT_CHANGED"))
{
log_print(HT_LOG_INFO, "%s, INFO_OUTPUT_FORMAT_CHANGED\r\n", __FUNCTION__);
}
else if (outputBufferIndex == QAndroidJniObject::getStaticField<jint>("android/media/MediaCodec", "INFO_TRY_AGAIN_LATER"))
{
log_print(HT_LOG_INFO, "%s, OUTPUT INFO_TRY_AGAIN_LATER, %d\r\n", __FUNCTION__, m_nOutputTimeout);
m_nOutputTimeout++;
}
else
{
log_print(HT_LOG_ERR, "%s, unknow index, %d\r\n", __FUNCTION__, outputBufferIndex);
}
// always, check/clear jni exceptions.
if (env->ExceptionCheck())
{
log_print(HT_LOG_ERR, "%s, ExceptionCheck\r\n", __FUNCTION__);
env->ExceptionClear();
}
if (m_nInputTimeout > 30 || m_nOutputTimeout > 30)
{
m_nInputTimeout = 0;
m_nOutputTimeout = 0;
log_print(HT_LOG_INFO, "%s, reset encoder start\r\n", __FUNCTION__);
uninit();
init(&m_EncoderParams);
log_print(HT_LOG_INFO, "%s, reset encoder end\r\n", __FUNCTION__);
}
return ret;
}
void CAndroidVideoEncoder::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)
{
p_cb->pCallback(data, size, 0, p_cb->pUserdata);
}
p_node = h_list_lookup_next(m_pCallbackList, p_node);
}
h_list_lookup_end(m_pCallbackList);
sys_os_mutex_leave(m_pCallbackMutex);
}
BOOL CAndroidVideoEncoder::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 CAndroidVideoEncoder::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 CAndroidVideoEncoder::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 * CAndroidVideoEncoder::getH264AuxSDPLine(int rtp_pt)
{
uint8 * sps = NULL; uint32 spsSize = 0;
uint8 * pps = NULL; uint32 ppsSize = 0;
if (NULL == m_pExtra || m_nExtraLen <= 8)
{
return NULL;
}
uint8 *r, *end = m_pExtra + m_nExtraLen;
r = avc_find_startcode(m_pExtra, 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 * CAndroidVideoEncoder::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_pExtra || m_nExtraLen < 12)
{
return NULL;
}
uint8 *r, *end = m_pExtra + m_nExtraLen;
r = avc_find_startcode(m_pExtra, 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 * CAndroidVideoEncoder::getAuxSDPLine(int rtp_pt)
{
if (VIDEO_CODEC_H264 == m_EncoderParams.DstCodec)
{
return getH264AuxSDPLine(rtp_pt);
}
else if (VIDEO_CODEC_H265 == m_EncoderParams.DstCodec)
{
return getH265AuxSDPLine(rtp_pt);
}
return NULL;
}
BOOL CAndroidVideoEncoder::getExtraData(uint8 ** extradata, int * extralen)
{
if (m_pExtra && m_nExtraLen > 0)
{
*extradata = m_pExtra;
*extralen = m_nExtraLen;
return TRUE;
}
return FALSE;
}