1083 lines
32 KiB
C++
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;
|
|
}
|
|
|
|
|
|
|