/*************************************************************************************** * * 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 #include 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 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("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("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("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()); if (env->ExceptionCheck()) { // Unsupported type? env->ExceptionClear(); continue; } QAndroidJniObject colorFormats = codecCaps.getObjectField("colorFormats", "[I"); jsize size = env->GetArrayLength(colorFormats.object()); std::vector color_formats; color_formats.resize(size); env->GetIntArrayRegion(colorFormats.object(), 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()); for (int j = 0; j < size; ++j) { jobject typeJava = env->GetObjectArrayElement(supportedTypes.object(), 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()); 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("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("android/media/MediaCodecInfo$CodecCapabilities", "COLOR_FormatYUV420Planar"); if (SupportColorFormat(color_formats, format)) { m_dstFmt = format; } } else { int format = QAndroidJniObject::getStaticField("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(), m_EncoderParams.DstWidth, m_EncoderParams.DstHeight); QAndroidJniObject KEY_BIT_RATE = QAndroidJniObject::getStaticObjectField("android/media/MediaFormat", "KEY_BIT_RATE", "Ljava/lang/String;"); mediaFormat.callMethod("setInteger", "(Ljava/lang/String;I)V", KEY_BIT_RATE.object(), bitrate); QAndroidJniObject KEY_FRAME_RATE = QAndroidJniObject::getStaticObjectField("android/media/MediaFormat", "KEY_FRAME_RATE", "Ljava/lang/String;"); mediaFormat.callMethod("setInteger", "(Ljava/lang/String;I)V", KEY_FRAME_RATE.object(), (int)m_EncoderParams.DstFramerate); QAndroidJniObject KEY_COLOR_FORMAT = QAndroidJniObject::getStaticObjectField("android/media/MediaFormat", "KEY_COLOR_FORMAT", "Ljava/lang/String;"); mediaFormat.callMethod("setInteger", "(Ljava/lang/String;I)V", KEY_COLOR_FORMAT.object(), m_dstFmt); QAndroidJniObject KEY_I_FRAME_INTERVAL = QAndroidJniObject::getStaticObjectField("android/media/MediaFormat", "KEY_I_FRAME_INTERVAL", "Ljava/lang/String;"); mediaFormat.callMethod("setInteger", "(Ljava/lang/String;I)V", KEY_I_FRAME_INTERVAL.object(), 1); if (VIDEO_CODEC_H264 == m_EncoderParams.DstCodec) { int profile = QAndroidJniObject::getStaticField("android/media/MediaCodecInfo$CodecProfileLevel", "AVCProfileBaseline"); mediaFormat.callMethod("setInteger", "(Ljava/lang/String;I)V", QAndroidJniObject::fromString("profile").object(), profile); int level = QAndroidJniObject::getStaticField("android/media/MediaCodecInfo$CodecProfileLevel", "AVCLevel31"); mediaFormat.callMethod("setInteger", "(Ljava/lang/String;I)V", QAndroidJniObject::fromString("level").object(), level); } else if (VIDEO_CODEC_H265 == m_EncoderParams.DstCodec) { int profile = QAndroidJniObject::getStaticField("android/media/MediaCodecInfo$CodecProfileLevel", "HEVCProfileMain"); mediaFormat.callMethod("setInteger", "(Ljava/lang/String;I)V", QAndroidJniObject::fromString("profile").object(), profile); int level = QAndroidJniObject::getStaticField("android/media/MediaCodecInfo$CodecProfileLevel", "HEVCHighTierLevel4"); mediaFormat.callMethod("setInteger", "(Ljava/lang/String;I)V", QAndroidJniObject::fromString("level").object(), level); } m_codec.callMethod("configure", "(Landroid/media/MediaFormat;Landroid/view/Surface;Landroid/media/MediaCrypto;I)V", mediaFormat.object(), NULL, NULL, QAndroidJniObject::getStaticField("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("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("stop", "()V"); if (env->ExceptionCheck()) { env->ExceptionClear(); } m_codec.callMethod("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("android/media/MediaCodecInfo$CodecCapabilities", "COLOR_FormatYUV420Planar"); formatYUV420SemiPlanar = QAndroidJniObject::getStaticField("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("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("capacity", "()I"); sizeTMP = buffsize > dstsize ? dstsize : buffsize; // fetch a pointer to the ByteBuffer backing store uint8 *dst_ptr = (uint8_t*)env->GetDirectBufferAddress(inputBuffer.object()); 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("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("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("dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I", bufferInfo.object(), 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()); if (dst_ptr) { dst_ptr += bufferInfo.getField("offset"); int buff_size = bufferInfo.getField("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("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("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("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("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; }