1165 lines
26 KiB
C++
1165 lines
26 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 "file_demux.h"
|
|
#include "audio_encoder.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"
|
|
|
|
|
|
/*********************************************************************************/
|
|
|
|
/**
|
|
* audio decode callback
|
|
*/
|
|
void AudioDecodeCallback(AVFrame * frame, void *pUserdata)
|
|
{
|
|
CFileDemux * pDemux = (CFileDemux *)pUserdata;
|
|
|
|
pDemux->audioEncode(frame);
|
|
}
|
|
|
|
/**
|
|
* video decode callback
|
|
*/
|
|
void VideoDecodeCallback(AVFrame * frame, void *pUserdata)
|
|
{
|
|
CFileDemux * pDemux = (CFileDemux *)pUserdata;
|
|
|
|
pDemux->videoEncode(frame);
|
|
}
|
|
|
|
/**
|
|
* audio encode callback
|
|
*/
|
|
void AudioEncodeCallback(uint8 *data, int size, int nbsamples, void *pUserdata)
|
|
{
|
|
CFileDemux * pDemux = (CFileDemux *)pUserdata;
|
|
|
|
pDemux->dataCallback(data, size, DATA_TYPE_AUDIO, nbsamples, 1);
|
|
}
|
|
|
|
/**
|
|
* video encode callback
|
|
*/
|
|
void VideoEncodeCallback(uint8 *data, int size, int waitnext, void *pUserdata)
|
|
{
|
|
CFileDemux * pDemux = (CFileDemux *)pUserdata;
|
|
|
|
pDemux->dataCallback(data, size, DATA_TYPE_VIDEO, 0, waitnext);
|
|
}
|
|
|
|
|
|
CFileDemux::CFileDemux(const char * filename, int loopnums)
|
|
{
|
|
m_nAudioIndex = -1;
|
|
m_nVideoIndex = -1;
|
|
m_nDuration = 0;
|
|
m_nCurPos = 0;
|
|
|
|
m_nNalLength = 0;
|
|
|
|
m_pFormatContext = NULL;
|
|
|
|
m_pVideoDecoder = NULL;
|
|
m_pAudioDecoder = NULL;
|
|
m_pVideoEncoder = NULL;
|
|
m_pAudioEncoder = NULL;
|
|
|
|
m_nPreNalu = -1;
|
|
m_bVideoFirst = TRUE;
|
|
m_bAudioFirst = TRUE;
|
|
m_pCallback = NULL;
|
|
m_pUserdata = NULL;
|
|
|
|
m_nLoopNums = 0;
|
|
m_nMaxLoopNums = loopnums;
|
|
|
|
memset(m_pFilename, 0, sizeof(m_pFilename));
|
|
strncpy(m_pFilename, filename, sizeof(m_pFilename)-1);
|
|
|
|
init(filename);
|
|
}
|
|
|
|
CFileDemux::~CFileDemux()
|
|
{
|
|
uninit();
|
|
}
|
|
|
|
/**
|
|
* Init file demux
|
|
*
|
|
* @param filename the file name
|
|
* @return TRUE on success, FALSE on error
|
|
*/
|
|
BOOL CFileDemux::init(const char * filename)
|
|
{
|
|
if (avformat_open_input(&m_pFormatContext, filename, NULL, NULL) != 0)
|
|
{
|
|
log_print(HT_LOG_ERR, "avformat_open_input failed, %s\r\n", filename);
|
|
return FALSE;
|
|
}
|
|
|
|
avformat_find_stream_info(m_pFormatContext, NULL);
|
|
|
|
if (m_pFormatContext->duration != AV_NOPTS_VALUE)
|
|
{
|
|
m_nDuration = m_pFormatContext->duration;
|
|
}
|
|
|
|
// find audio & video stream index
|
|
for (uint32 i=0; i < m_pFormatContext->nb_streams; i++)
|
|
{
|
|
if (m_pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
|
|
{
|
|
m_nVideoIndex = i;
|
|
|
|
if (m_nDuration < m_pFormatContext->streams[i]->duration)
|
|
{
|
|
m_nDuration = m_pFormatContext->streams[i]->duration;
|
|
}
|
|
}
|
|
else if (m_pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
|
|
{
|
|
m_nAudioIndex = i;
|
|
|
|
if (m_nDuration < m_pFormatContext->streams[i]->duration)
|
|
{
|
|
m_nDuration = m_pFormatContext->streams[i]->duration;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_nDuration /= 1000; // to millisecond
|
|
|
|
// has video stream
|
|
if (m_nVideoIndex != -1)
|
|
{
|
|
AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;
|
|
|
|
if (codecpar->codec_id == AV_CODEC_ID_H264)
|
|
{
|
|
if (codecpar->extradata && codecpar->extradata_size > 8)
|
|
{
|
|
if (codecpar->extradata[0] == 1)
|
|
{
|
|
// Store right nal length size that will be used to parse all other nals
|
|
m_nNalLength = (codecpar->extradata[4] & 0x03) + 1;
|
|
}
|
|
|
|
const AVBitStreamFilter * bsfc = av_bsf_get_by_name("h264_mp4toannexb");
|
|
if (bsfc)
|
|
{
|
|
int ret;
|
|
|
|
AVBSFContext *bsf;
|
|
av_bsf_alloc(bsfc, &bsf);
|
|
|
|
ret = avcodec_parameters_copy(bsf->par_in, codecpar);
|
|
if (ret < 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
ret = av_bsf_init(bsf);
|
|
if (ret < 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
ret = avcodec_parameters_copy(codecpar, bsf->par_out);
|
|
if (ret < 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
av_bsf_free(&bsf);
|
|
}
|
|
}
|
|
}
|
|
else if (codecpar->codec_id == AV_CODEC_ID_HEVC)
|
|
{
|
|
if (codecpar->extradata && codecpar->extradata_size > 8)
|
|
{
|
|
if (codecpar->extradata[0] || codecpar->extradata[1] || codecpar->extradata[2] > 1)
|
|
{
|
|
m_nNalLength = 4;
|
|
}
|
|
|
|
const AVBitStreamFilter * bsfc = av_bsf_get_by_name("hevc_mp4toannexb");
|
|
if (bsfc)
|
|
{
|
|
int ret;
|
|
|
|
AVBSFContext *bsf;
|
|
av_bsf_alloc(bsfc, &bsf);
|
|
|
|
ret = avcodec_parameters_copy(bsf->par_in, codecpar);
|
|
if (ret < 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
ret = av_bsf_init(bsf);
|
|
if (ret < 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
ret = avcodec_parameters_copy(codecpar, bsf->par_out);
|
|
if (ret < 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
av_bsf_free(&bsf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CFileDemux::uninit()
|
|
{
|
|
if (m_pVideoDecoder)
|
|
{
|
|
delete m_pVideoDecoder;
|
|
m_pVideoDecoder = NULL;
|
|
}
|
|
|
|
if (m_pAudioDecoder)
|
|
{
|
|
delete m_pAudioDecoder;
|
|
m_pAudioDecoder = NULL;
|
|
}
|
|
|
|
if (m_pVideoEncoder)
|
|
{
|
|
delete m_pVideoEncoder;
|
|
m_pVideoEncoder = NULL;
|
|
}
|
|
|
|
if (m_pAudioEncoder)
|
|
{
|
|
delete m_pAudioEncoder;
|
|
m_pAudioEncoder = NULL;
|
|
}
|
|
|
|
if (m_pFormatContext)
|
|
{
|
|
avformat_close_input(&m_pFormatContext);
|
|
}
|
|
}
|
|
|
|
enum AVCodecID CFileDemux::getVideoCodec()
|
|
{
|
|
if (m_nVideoIndex != -1)
|
|
{
|
|
return m_pFormatContext->streams[m_nVideoIndex]->codecpar->codec_id;
|
|
}
|
|
|
|
return AV_CODEC_ID_NONE;
|
|
}
|
|
|
|
int CFileDemux::getWidth()
|
|
{
|
|
if (m_nVideoIndex != -1)
|
|
{
|
|
return m_pFormatContext->streams[m_nVideoIndex]->codecpar->width;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int CFileDemux::getHeight()
|
|
{
|
|
if (m_nVideoIndex != -1)
|
|
{
|
|
return m_pFormatContext->streams[m_nVideoIndex]->codecpar->height;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
double CFileDemux::getFramerate()
|
|
{
|
|
double framerate = 25;
|
|
|
|
if (m_nVideoIndex != -1)
|
|
{
|
|
if (m_pFormatContext->streams[m_nVideoIndex]->r_frame_rate.den > 0)
|
|
{
|
|
framerate = m_pFormatContext->streams[m_nVideoIndex]->r_frame_rate.num /
|
|
(double)m_pFormatContext->streams[m_nVideoIndex]->r_frame_rate.den;
|
|
}
|
|
|
|
if ((framerate < 1 || framerate > 60) &&
|
|
m_pFormatContext->streams[m_nVideoIndex]->avg_frame_rate.den > 0)
|
|
{
|
|
framerate = m_pFormatContext->streams[m_nVideoIndex]->avg_frame_rate.num /
|
|
(double)m_pFormatContext->streams[m_nVideoIndex]->avg_frame_rate.den;
|
|
}
|
|
|
|
if (framerate < 1 || framerate > 60)
|
|
{
|
|
framerate = 25;
|
|
}
|
|
}
|
|
|
|
return framerate;
|
|
}
|
|
|
|
enum AVCodecID CFileDemux::getAudioCodec()
|
|
{
|
|
if (m_nAudioIndex != -1)
|
|
{
|
|
return m_pFormatContext->streams[m_nAudioIndex]->codecpar->codec_id;
|
|
}
|
|
|
|
return AV_CODEC_ID_NONE;
|
|
}
|
|
|
|
int CFileDemux::getSamplerate()
|
|
{
|
|
if (m_nAudioIndex != -1)
|
|
{
|
|
return m_pFormatContext->streams[m_nAudioIndex]->codecpar->sample_rate;
|
|
}
|
|
|
|
return 8000;
|
|
}
|
|
|
|
int CFileDemux::getChannels()
|
|
{
|
|
if (m_nAudioIndex != -1)
|
|
{
|
|
return m_pFormatContext->streams[m_nAudioIndex]->codecpar->channels;
|
|
}
|
|
|
|
return 2;
|
|
}
|
|
|
|
/**
|
|
* set audio output parameters
|
|
*
|
|
* @param codec audio output codec
|
|
* @param samplerate audio output sample rate
|
|
* @param channels audio output channels
|
|
* @return TRUE on success, FALSE on error
|
|
*/
|
|
BOOL CFileDemux::setAudioFormat(int codec, int samplerate, int channels, int bitrate)
|
|
{
|
|
if (m_nAudioIndex == -1)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
AVCodecParameters * codecpar = m_pFormatContext->streams[m_nAudioIndex]->codecpar;
|
|
|
|
#if 0
|
|
if (codecpar->codec_id != to_audio_avcodecid(codec) ||
|
|
codecpar->channels != channels ||
|
|
codecpar->sample_rate != samplerate ||
|
|
codecpar->format != AV_SAMPLE_FMT_S16)
|
|
#else
|
|
if (1)
|
|
#endif
|
|
{
|
|
m_pAudioDecoder = new CAudioDecoder;
|
|
if (NULL == m_pAudioDecoder)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (FALSE == m_pAudioDecoder->init(codecpar->codec_id, codecpar->sample_rate, codecpar->channels, codecpar->bits_per_coded_sample))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
m_pAudioDecoder->setCallback(AudioDecodeCallback, this);
|
|
|
|
m_pAudioEncoder = new CAudioEncoder;
|
|
if (NULL == m_pAudioEncoder)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
AudioEncoderParam params;
|
|
memset(¶ms, 0, sizeof(params));
|
|
|
|
params.SrcChannels = codecpar->channels;
|
|
params.SrcSamplerate = codecpar->sample_rate;
|
|
params.SrcSamplefmt = AV_SAMPLE_FMT_S16;
|
|
params.DstChannels = channels;
|
|
params.DstSamplefmt = AV_SAMPLE_FMT_S16;
|
|
params.DstSamplerate = samplerate;
|
|
params.DstBitrate = bitrate;
|
|
params.DstCodec = codec;
|
|
|
|
if (FALSE == m_pAudioEncoder->init(¶ms))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
m_pAudioEncoder->addCallback(AudioEncodeCallback, this);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CFileDemux::setVideoFormat(int codec, int width, int height, double framerate, int bitrate)
|
|
{
|
|
if (m_nVideoIndex == -1)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;
|
|
|
|
if (codecpar->codec_id != to_video_avcodecid(codec) ||
|
|
codecpar->width != width ||
|
|
codecpar->height != height)
|
|
{
|
|
m_pVideoDecoder = new CVideoDecoder;
|
|
if (NULL == m_pVideoDecoder)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (FALSE == m_pVideoDecoder->init(codecpar->codec_id, codecpar->extradata, codecpar->extradata_size))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
m_pVideoDecoder->setCallback(VideoDecodeCallback, this);
|
|
|
|
m_pVideoEncoder = new CVideoEncoder;
|
|
if (NULL == m_pVideoEncoder)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
VideoEncoderParam params;
|
|
memset(¶ms, 0, sizeof(params));
|
|
|
|
params.SrcWidth = codecpar->width;
|
|
params.SrcHeight = codecpar->height;
|
|
params.SrcPixFmt = (AVPixelFormat)codecpar->format;
|
|
params.DstCodec = codec;
|
|
params.DstWidth = width;
|
|
params.DstHeight = height;
|
|
params.DstFramerate = framerate;
|
|
params.DstBitrate = bitrate;
|
|
|
|
if (FALSE == m_pVideoEncoder->init(¶ms))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
m_pVideoEncoder->addCallback(VideoEncodeCallback, this);
|
|
}
|
|
|
|
if (m_pVideoDecoder && codecpar->extradata_size > 0)
|
|
{
|
|
m_pVideoDecoder->decode(codecpar->extradata, codecpar->extradata_size, 0);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CFileDemux::videoDecode(AVPacket * pkt)
|
|
{
|
|
if (m_pVideoDecoder)
|
|
{
|
|
m_pVideoDecoder->decode(pkt);
|
|
}
|
|
}
|
|
|
|
void CFileDemux::audioDecode(AVPacket * pkt)
|
|
{
|
|
if (m_pAudioDecoder)
|
|
{
|
|
m_pAudioDecoder->decode(pkt);
|
|
}
|
|
}
|
|
|
|
void CFileDemux::videoEncode(AVFrame * frame)
|
|
{
|
|
if (m_pVideoEncoder)
|
|
{
|
|
m_pVideoEncoder->encode(frame);
|
|
}
|
|
}
|
|
|
|
void CFileDemux::audioEncode(AVFrame * frame)
|
|
{
|
|
if (m_pAudioEncoder)
|
|
{
|
|
m_pAudioEncoder->encode(frame);
|
|
}
|
|
}
|
|
|
|
void CFileDemux::videoData(uint8 * data, int size, uint64_t pts, int waitnext)
|
|
{
|
|
if (NULL == m_pVideoDecoder)
|
|
{
|
|
dataCallback(data, size, DATA_TYPE_VIDEO, 0, waitnext);
|
|
}
|
|
else
|
|
{
|
|
m_pVideoDecoder->decode(data, size, pts);
|
|
}
|
|
}
|
|
|
|
BOOL CFileDemux::readFrame()
|
|
{
|
|
int rret = 0;
|
|
AVPacket pkt;
|
|
|
|
av_init_packet(&pkt);
|
|
pkt.data = 0;
|
|
pkt.size = 0;
|
|
|
|
rret = av_read_frame(m_pFormatContext, &pkt);
|
|
if (AVERROR_EOF == rret)
|
|
{
|
|
if (++m_nLoopNums >= m_nMaxLoopNums)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (m_pFormatContext)
|
|
{
|
|
avformat_close_input(&m_pFormatContext);
|
|
}
|
|
|
|
return init(m_pFilename);
|
|
}
|
|
else if (0 != rret)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
int64 pts = AV_NOPTS_VALUE;
|
|
|
|
if (pkt.dts != AV_NOPTS_VALUE)
|
|
{
|
|
pts = pkt.dts;
|
|
|
|
if (m_pFormatContext->start_time != AV_NOPTS_VALUE)
|
|
{
|
|
pts -= m_pFormatContext->start_time;
|
|
}
|
|
}
|
|
else if (pkt.dts != AV_NOPTS_VALUE)
|
|
{
|
|
pts = pkt.dts;
|
|
|
|
if (m_pFormatContext->start_time != AV_NOPTS_VALUE)
|
|
{
|
|
pts -= m_pFormatContext->start_time;
|
|
}
|
|
}
|
|
|
|
if (pkt.stream_index == m_nVideoIndex)
|
|
{
|
|
AVRational q = {1, AV_TIME_BASE};
|
|
|
|
if (pts != AV_NOPTS_VALUE)
|
|
{
|
|
m_nCurPos = av_rescale_q (pts, m_pFormatContext->streams[m_nVideoIndex]->time_base, q);
|
|
m_nCurPos /= 1000;
|
|
}
|
|
|
|
AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;
|
|
|
|
if (codecpar->codec_id == AV_CODEC_ID_H264 || codecpar->codec_id == AV_CODEC_ID_HEVC)
|
|
{
|
|
if (m_nNalLength)
|
|
{
|
|
uint8 * data = pkt.data;
|
|
int size = pkt.size;
|
|
|
|
while (pkt.size >= m_nNalLength)
|
|
{
|
|
int len = 0;
|
|
int nal_length = m_nNalLength;
|
|
uint8 * pdata = pkt.data;
|
|
|
|
while (nal_length--)
|
|
{
|
|
len = (len << 8) | *pdata++;
|
|
}
|
|
|
|
if (len > pkt.size - m_nNalLength || len <= 0)
|
|
{
|
|
log_print(HT_LOG_DBG, "len=%d, pkt.size=%d\r\n", len, pkt.size);
|
|
break;
|
|
}
|
|
|
|
nal_length = m_nNalLength;
|
|
pkt.data[nal_length-1] = 1;
|
|
nal_length--;
|
|
while (nal_length--)
|
|
{
|
|
pkt.data[nal_length] = 0;
|
|
}
|
|
|
|
pkt.data += len + m_nNalLength;
|
|
pkt.size -= len + m_nNalLength;
|
|
}
|
|
|
|
videoData(data, size, pts, 1);
|
|
}
|
|
else if (pkt.data[0] == 0 && pkt.data[1] == 0 && pkt.data[2] == 0 && pkt.data[3] == 1)
|
|
{
|
|
videoData(pkt.data, pkt.size, pts, 1);
|
|
}
|
|
else if (pkt.data[0] == 0 && pkt.data[1] == 0 && pkt.data[2] == 1)
|
|
{
|
|
videoData(pkt.data, pkt.size, pts, 1);
|
|
}
|
|
else
|
|
{
|
|
log_print(HT_LOG_ERR, "%s, unknown format\r\n", __FUNCTION__);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
videoData(pkt.data, pkt.size, pts, 1);
|
|
}
|
|
}
|
|
else if (pkt.stream_index == m_nAudioIndex)
|
|
{
|
|
AVRational q = {1, AV_TIME_BASE};
|
|
|
|
if (pts != AV_NOPTS_VALUE)
|
|
{
|
|
m_nCurPos = av_rescale_q (pts, m_pFormatContext->streams[m_nAudioIndex]->time_base, q);
|
|
m_nCurPos /= 1000;
|
|
}
|
|
|
|
if (NULL == m_pAudioEncoder)
|
|
{
|
|
dataCallback(pkt.data, pkt.size, DATA_TYPE_AUDIO, 1024, 1);
|
|
}
|
|
else
|
|
{
|
|
audioDecode(&pkt);
|
|
}
|
|
}
|
|
|
|
av_packet_unref(&pkt);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
char * CFileDemux::getH264AuxSDPLine(int rtp_pt)
|
|
{
|
|
uint8 * sps = NULL; uint32 spsSize = 0;
|
|
uint8 * pps = NULL; uint32 ppsSize = 0;
|
|
|
|
AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;
|
|
|
|
if (codecpar->extradata_size <= 8)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
uint8 *r, *end = codecpar->extradata + codecpar->extradata_size;
|
|
r = avc_find_startcode(codecpar->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 * CFileDemux::getH265AuxSDPLine(int rtp_pt)
|
|
{
|
|
uint8* vps = NULL; uint32 vpsSize = 0;
|
|
uint8* sps = NULL; uint32 spsSize = 0;
|
|
uint8* pps = NULL; uint32 ppsSize = 0;
|
|
|
|
AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;
|
|
|
|
if (codecpar->extradata_size < 23)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
uint8 *r, *end = codecpar->extradata + codecpar->extradata_size;
|
|
r = avc_find_startcode(codecpar->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 * CFileDemux::getMP4AuxSDPLine(int rtp_pt)
|
|
{
|
|
AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;
|
|
|
|
if (codecpar->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*codecpar->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 < codecpar->extradata_size; ++i)
|
|
{
|
|
sprintf(endPtr, "%02X", codecpar->extradata[i]);
|
|
endPtr += 2;
|
|
}
|
|
|
|
return fmtp;
|
|
}
|
|
|
|
char * CFileDemux::getVideoAuxSDPLine(int rtp_pt)
|
|
{
|
|
if (m_pVideoEncoder)
|
|
{
|
|
return m_pVideoEncoder->getAuxSDPLine(rtp_pt);
|
|
}
|
|
|
|
if (m_nVideoIndex < 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;
|
|
|
|
if (codecpar->codec_id == AV_CODEC_ID_H264)
|
|
{
|
|
return getH264AuxSDPLine(rtp_pt);
|
|
}
|
|
else if (codecpar->codec_id == AV_CODEC_ID_MPEG4)
|
|
{
|
|
return getMP4AuxSDPLine(rtp_pt);
|
|
}
|
|
else if (codecpar->codec_id == AV_CODEC_ID_HEVC)
|
|
{
|
|
return getH265AuxSDPLine(rtp_pt);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
char * CFileDemux::getAACAuxSDPLine(int rtp_pt)
|
|
{
|
|
AVCodecParameters * codecpar = m_pFormatContext->streams[m_nAudioIndex]->codecpar;
|
|
|
|
if (codecpar->extradata_size == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
char const* fmtpFmt =
|
|
"a=fmtp:%d "
|
|
"streamtype=5;profile-level-id=1;"
|
|
"mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;"
|
|
"config="; /* streamtype, 4 : video, 5 : audio */
|
|
uint32 fmtpFmtSize = strlen(fmtpFmt)
|
|
+ 3 /* max char len */
|
|
+ 2*codecpar->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);
|
|
char* endPtr = &fmtp[strlen(fmtp)];
|
|
for (int i = 0; i < codecpar->extradata_size; ++i)
|
|
{
|
|
sprintf(endPtr, "%02X", codecpar->extradata[i]);
|
|
endPtr += 2;
|
|
}
|
|
|
|
return fmtp;
|
|
}
|
|
|
|
char * CFileDemux::getAudioAuxSDPLine(int rtp_pt)
|
|
{
|
|
if (m_pAudioEncoder)
|
|
{
|
|
return m_pAudioEncoder->getAuxSDPLine(rtp_pt);
|
|
}
|
|
|
|
if (m_nAudioIndex < 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
AVCodecParameters * codecpar = m_pFormatContext->streams[m_nAudioIndex]->codecpar;
|
|
|
|
if (codecpar->codec_id == AV_CODEC_ID_AAC)
|
|
{
|
|
return getAACAuxSDPLine(rtp_pt);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
BOOL CFileDemux::seekStream(double pos)
|
|
{
|
|
if (pos < 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (pos * 1000 == m_nCurPos)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
int stream = -1;
|
|
int64 seekpos = pos * 1000000;
|
|
|
|
if (m_nVideoIndex >= 0)
|
|
{
|
|
stream = m_nVideoIndex;
|
|
}
|
|
else if (m_nAudioIndex >= 0)
|
|
{
|
|
stream = m_nAudioIndex;
|
|
}
|
|
|
|
if (m_pFormatContext->start_time != AV_NOPTS_VALUE)
|
|
{
|
|
seekpos += m_pFormatContext->start_time;
|
|
}
|
|
|
|
if (stream >= 0)
|
|
{
|
|
AVRational q = {1, AV_TIME_BASE};
|
|
|
|
seekpos = av_rescale_q(seekpos, q, m_pFormatContext->streams[stream]->time_base);
|
|
}
|
|
|
|
if (av_seek_frame(m_pFormatContext, stream, seekpos, AVSEEK_FLAG_BACKWARD) < 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// Accurate seek to the specified position
|
|
AVPacket pkt;
|
|
|
|
av_init_packet(&pkt);
|
|
pkt.data = 0;
|
|
pkt.size = 0;
|
|
|
|
while (av_read_frame(m_pFormatContext, &pkt) == 0)
|
|
{
|
|
if (pkt.stream_index != stream)
|
|
{
|
|
av_packet_unref(&pkt);
|
|
continue;
|
|
}
|
|
|
|
if (pkt.pts != AV_NOPTS_VALUE)
|
|
{
|
|
if (pkt.pts < seekpos)
|
|
{
|
|
av_packet_unref(&pkt);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if (pkt.dts != AV_NOPTS_VALUE)
|
|
{
|
|
if (pkt.dts < seekpos)
|
|
{
|
|
av_packet_unref(&pkt);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
av_packet_unref(&pkt);
|
|
}
|
|
|
|
av_packet_unref(&pkt);
|
|
|
|
m_nCurPos = pos * 1000;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CFileDemux::dataCallback(uint8 * data, int size, int type, int nbsamples, BOOL waitnext)
|
|
{
|
|
if (m_pCallback)
|
|
{
|
|
uint8 * buff = data;
|
|
int len = size;
|
|
|
|
if (DATA_TYPE_VIDEO == type && NULL == m_pVideoEncoder && m_pFormatContext && m_nVideoIndex >= 0)
|
|
{
|
|
AVCodecParameters * codecpar = m_pFormatContext->streams[m_nVideoIndex]->codecpar;
|
|
|
|
if (codecpar->codec_id == AV_CODEC_ID_H264 && codecpar->extradata_size > 8)
|
|
{
|
|
uint8 naltype = avc_h264_nalu_type(data, size);
|
|
if (naltype == H264_NAL_IDR)
|
|
{
|
|
len = codecpar->extradata_size + size;
|
|
buff = (uint8 *)malloc(len);
|
|
if (buff)
|
|
{
|
|
memcpy(buff, codecpar->extradata, codecpar->extradata_size);
|
|
memcpy(buff + codecpar->extradata_size, data, size);
|
|
}
|
|
else
|
|
{
|
|
buff = data;
|
|
len = size;
|
|
}
|
|
}
|
|
}
|
|
else if (codecpar->codec_id == AV_CODEC_ID_HEVC && codecpar->extradata_size > 8)
|
|
{
|
|
uint8 naltype = avc_h265_nalu_type(data, size);
|
|
if (naltype >= HEVC_NAL_BLA_W_LP && naltype <= HEVC_NAL_CRA_NUT)
|
|
{
|
|
len = codecpar->extradata_size + size;
|
|
buff = (uint8 *)malloc(len);
|
|
if (buff)
|
|
{
|
|
memcpy(buff, codecpar->extradata, codecpar->extradata_size);
|
|
memcpy(buff + codecpar->extradata_size, data, size);
|
|
}
|
|
else
|
|
{
|
|
buff = data;
|
|
len = size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_pCallback(buff, len, type, nbsamples, waitnext, m_pUserdata);
|
|
|
|
if (buff != data)
|
|
{
|
|
free(buff);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CFileDemux::setCallback(DemuxCallback pCallback, void * pUserdata)
|
|
{
|
|
m_pCallback = pCallback;
|
|
m_pUserdata = pUserdata;
|
|
}
|
|
|
|
|
|
|