#if defined(_HAVE_FFMPEG_) #include "ffmpeg-file-source.h" #include "rtp-profile.h" #include "rtp-payload.h" #include "mov-format.h" #include "mpeg4-avc.h" #include "mpeg4-aac.h" #include "sys/system.h" #include "sys/path.h" #include "base64.h" #include "rtp.h" #include extern "C" uint32_t rtp_ssrc(void); extern "C" const struct mov_buffer_t* mov_file_buffer(void); extern "C" int sdp_h264(uint8_t *data, int bytes, const char* proto, unsigned short port, int payload, int frequence, const void* extra, int extra_size); extern "C" int sdp_h265(uint8_t *data, int bytes, const char* proto, unsigned short port, int payload, int frequence, const void* extra, int extra_size); extern "C" int sdp_mpeg4_es(uint8_t *data, int bytes, const char* proto, unsigned short port, int payload, int frequence, const void* extra, int extra_size); extern "C" int sdp_aac_latm(uint8_t *data, int bytes, const char* proto, unsigned short port, int payload, int sample_rate, int channel_count, const void* extra, int extra_size); extern "C" int sdp_aac_generic(uint8_t *data, int bytes, const char* proto, unsigned short port, int payload, int sample_rate, int channel_count, const void* extra, int extra_size); extern "C" int sdp_opus(uint8_t *data, int bytes, const char* proto, unsigned short port, int payload, int sample_rate, int channel_count, const void* extra, int extra_size); extern "C" int sdp_g711u(uint8_t *data, int bytes, const char* proto, unsigned short port); extern "C" int sdp_g711a(uint8_t *data, int bytes, const char* proto, unsigned short port); inline uint8_t ffmpeg_codec_id_2_mp4_object(AVCodecID codecid) { switch (codecid) { case AV_CODEC_ID_MPEG4: return MOV_OBJECT_MP4V; case AV_CODEC_ID_H264: return MOV_OBJECT_H264; case AV_CODEC_ID_HEVC: return MOV_OBJECT_HEVC; case AV_CODEC_ID_AAC: return MOV_OBJECT_AAC; case AV_CODEC_ID_OPUS: return MOV_OBJECT_OPUS; default: return 0; } } FFFileSource::FFFileSource(const char *file) { static int s_init = 0; if(0 == s_init) { s_init = 1; avformat_network_init(); } m_speed = 1.0; m_status = 0; m_clock = 0; m_count = 0; m_dts = -1; av_init_packet(&m_pkt); if (0 == Open(file)) { for (unsigned int i = 0; i < m_ic->nb_streams; i++) { AVCodecParameters* codecpar = m_ic->streams[i]->codecpar; uint8_t object = ffmpeg_codec_id_2_mp4_object(codecpar->codec_id); if (0 == object) { // assert(0); continue; } if (AVMEDIA_TYPE_VIDEO == codecpar->codec_type) { MP4OnVideo(this, i, object, codecpar->width, codecpar->height, codecpar->extradata, codecpar->extradata_size); } else if (AVMEDIA_TYPE_AUDIO == codecpar->codec_type) { MP4OnAudio(this, i, object, codecpar->channels, codecpar->bits_per_raw_sample, codecpar->sample_rate, codecpar->extradata, codecpar->extradata_size); } } } for (int i = 0; i < m_count; i++) { struct media_t* m = &m_media[i]; rtp_set_info(m->rtp, "RTSPServer", path_basename(file)); } } FFFileSource::~FFFileSource() { for (int i = 0; i < m_count; i++) { struct media_t* m = &m_media[i]; if (m->rtp) { rtp_destroy(m->rtp); m->rtp = NULL; } if (m->packer) { rtp_payload_encode_destroy(m->packer); m->packer = NULL; } } if (m_ic) { avformat_close_input(&m_ic); avformat_free_context(m_ic); } } int FFFileSource::Open(const char* file) { int r; AVDictionary* opt = NULL; m_ic = avformat_alloc_context(); if (NULL == m_ic) { printf("%s(%s): avformat_alloc_context failed.\n", __FUNCTION__, file); return -ENOMEM; } //if (!av_dict_get(ff->opt, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) { // av_dict_set(&ff->opt, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); // scan_all_pmts_set = 1; //} r = avformat_open_input(&m_ic, file, NULL, NULL/*&opt*/); if (0 != r) { printf("%s: avformat_open_input(%s) => %d\n", __FUNCTION__, file, r); return r; } //if (scan_all_pmts_set) // av_dict_set(&format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); //ff->ic->probesize = 100 * 1024; //ff->ic->max_analyze_duration = 5 * AV_TIME_BASE; /* If not enough info to get the stream parameters, we decode the first frames to get it. (used in mpeg case for example) */ r = avformat_find_stream_info(m_ic, NULL/*&opt*/); if (r < 0) { printf("%s(%s): could not find codec parameters\n", __FUNCTION__, file); return r; } av_dict_free(&opt); return 0; } int FFFileSource::SetTransport(const char* track, std::shared_ptr transport) { int t = atoi(track + 5/*track*/); for (int i = 0; i < m_count; i++) { struct media_t* m = &m_media[i]; if (t != m->track) continue; m->transport = transport; return 0; } return -1; } int FFFileSource::Play() { bool sendframe = false; if (3 == m_status) return 0; SEND_PACKET: if (0 == m_pkt.buf) { int r = av_read_frame(m_ic, &m_pkt); if (r == AVERROR_EOF) { // 0-EOF m_status = 3; SendBye(); return 0; } else if (r < 0) { // error return r; } for (r = 0; r < m_count; r++) { struct media_t* m = &m_media[r]; if (m->track == m_pkt.stream_index) break; } if (r == m_count) { av_packet_unref(&m_pkt); // send flag sendframe = 1; goto SEND_PACKET; } AVRational time_base = { 1, 1000/*ms*/ }; m_pkt.dts = (AV_NOPTS_VALUE == m_pkt.dts ? m_pkt.pts : m_pkt.dts); m_pkt.pts = (AV_NOPTS_VALUE == m_pkt.pts ? m_pkt.dts : m_pkt.pts); m_pkt.dts = av_rescale_q(m_pkt.dts, m_ic->streams[m_pkt.stream_index]->time_base, time_base); m_pkt.pts = av_rescale_q(m_pkt.pts, m_ic->streams[m_pkt.stream_index]->time_base, time_base); } m_status = 1; uint64_t clock = system_time(); for (int i = 0; i < m_count; i++) { struct media_t* m = &m_media[i]; if (m->track != m_pkt.stream_index) continue; if (0 == m_clock || m_clock > clock) m_clock = clock; if (-1 == m_dts) m_dts = m_pkt.dts; if (int64_t(clock - m_clock) + m_dts >= m_pkt.dts) { if (0 == strcmp("H264", m->name) || 0 == strcmp("H265", m->name)) { // MPEG4 -> H.264 byte stream uint8_t* p = m_pkt.data; size_t bytes = m_pkt.size; while (bytes > 0) { // nalu size -> start code assert(bytes > 4); uint32_t n = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; p[0] = 0; p[1] = 0; p[2] = 0; p[3] = 1; bytes -= n + 4; p += n + 4; } //printf("[V] pts: %lld, dts: %lld, clock: %llu\n", m_pkt.pts, m_pkt.dts, clock); } else if (0 == strcmp("MP4A-LATM", m->name) || 0 == strcmp("MPEG4-GENERIC", m->name)) { // add ADTS header //printf("[A] pts: %lld, dts: %lld, clock: %llu\n", m_pkt.pts, m_pkt.dts, clock); } else { assert(0); } if (-1 == m->dts_first) m->dts_first = m_pkt.pts; m->dts_last = m_pkt.pts; uint32_t timestamp = m->timestamp + (uint32_t)((m->dts_last - m->dts_first) * (m->frequency / 1000) /*kHz*/); //printf("[%s] pts: %lld, dts: %lld, timestamp: %u(%u)\n", m->name, m_pkt.pts, m_pkt.dts, (unsigned int)timestamp, (unsigned int)m->timestamp); rtp_payload_encode_input(m->packer, m_pkt.data, m_pkt.size, timestamp); av_packet_unref(&m_pkt); // send flag sendframe = 1; goto SEND_PACKET; } break; } return sendframe ? 1 : 0; } int FFFileSource::Pause() { m_status = 2; m_clock = 0; m_dts = -1; return 0; } int FFFileSource::Seek(int64_t pos) { // update timestamp for (int i = 0; i < m_count; i++) { if (-1 != m_media[i].dts_first) m_media[i].timestamp += m_media[i].dts_last - m_media[i].dts_first + 1; m_media[i].dts_first = -1; //SendRTCP(&m_media[i], system_time()); } m_dts = -1; m_clock = 0; av_packet_unref(&m_pkt); // clear buffered frame AVRational time_base = { 1, 1000/*ms*/ }; pos = av_rescale_q(pos, time_base, m_ic->streams[0]->time_base); return av_seek_frame(m_ic, 0, pos, 0); } int FFFileSource::SetSpeed(double speed) { m_speed = speed; return 0; } int FFFileSource::GetDuration(int64_t& duration) const { if (m_ic) { duration = m_ic->duration / 1000; return 0; } return -1; } int FFFileSource::GetSDPMedia(std::string& sdp) const { sdp = m_sdp; return m_ic ? 0 : -1; } int FFFileSource::GetRTPInfo(const char* uri, char *rtpinfo, size_t bytes) const { int n = 0; uint16_t seq; uint32_t timestamp; // RTP-Info: url=rtsp://foo.com/bar.avi/streamid=0;seq=45102, // url=rtsp://foo.com/bar.avi/streamid=1;seq=30211 for (int i = 0; i < m_count; i++) { const struct media_t* m = &m_media[i]; rtp_payload_encode_getinfo(m->packer, &seq, ×tamp); if (i > 0) rtpinfo[n++] = ','; n += snprintf(rtpinfo + n, bytes - n, "url=%s/track%d;seq=%hu;rtptime=%u", uri, m->track, seq, (unsigned int)(m->timestamp * (m->frequency / 1000) /*kHz*/)); } return 0; } void FFFileSource::MP4OnVideo(void* param, uint32_t track, uint8_t object, int /*width*/, int /*height*/, const void* extra, size_t bytes) { int n = 0; uint8_t buffer[8 * 1024]; FFFileSource* self = (FFFileSource*)param; struct media_t* m = &self->m_media[self->m_count++]; m->track = track; m->rtcp_clock = 0; m->ssrc = rtp_ssrc(); m->timestamp = rtp_ssrc(); m->bandwidth = 4 * 1024 * 1024; m->dts_last = m->dts_first = -1; if (MOV_OBJECT_H264 == object) { m->frequency = 90000; m->payload = RTP_PAYLOAD_H264; snprintf(m->name, sizeof(m->name), "%s", "H264"); n = sdp_h264(buffer, sizeof(buffer), "RTP/AVP", 0, RTP_PAYLOAD_H264, 90000, extra, bytes); } else if (MOV_OBJECT_HEVC == object) { m->frequency = 90000; m->payload = RTP_PAYLOAD_H265; snprintf(m->name, sizeof(m->name), "%s", "H265"); n = sdp_h265(buffer, sizeof(buffer), "RTP/AVP", 0, RTP_PAYLOAD_H265, 90000, extra, bytes); } else { assert(0); return; } struct rtp_payload_t rtpfunc = { FFFileSource::RTPAlloc, FFFileSource::RTPFree, FFFileSource::RTPPacket, }; m->packer = rtp_payload_encode_create(m->payload, m->name, (uint16_t)m->timestamp, m->ssrc, &rtpfunc, m); struct rtp_event_t event; event.on_rtcp = OnRTCPEvent; m->rtp = rtp_create(&event, self, m->ssrc, m->timestamp, m->frequency, m->bandwidth, 1); n += snprintf((char*)buffer + n, sizeof(buffer) - n, "a=control:track%d\n", m->track); self->m_sdp += (const char*)buffer; } void FFFileSource::MP4OnAudio(void* param, uint32_t track, uint8_t object, int channel_count, int /*bit_per_sample*/, int sample_rate, const void* extra, size_t bytes) { int n = 0; uint8_t buffer[2 * 1024]; FFFileSource* self = (FFFileSource*)param; struct media_t* m = &self->m_media[self->m_count++]; m->track = track; m->rtcp_clock = 0; m->ssrc = rtp_ssrc(); m->timestamp = rtp_ssrc(); m->bandwidth = 128 * 1024; m->dts_last = m->dts_first = -1; if (MOV_OBJECT_AAC == object) { struct mpeg4_aac_t aac; //aac.profile = MPEG4_AAC_LC; //aac.channel_configuration = (uint8_t)channel_count; //aac.sampling_frequency_index = (uint8_t)mpeg4_aac_audio_frequency_from(sample_rate); mpeg4_aac_audio_specific_config_load((const uint8_t*)extra, bytes, &aac); //assert(aac.sampling_frequency_index == (uint8_t)mpeg4_aac_audio_frequency_from(sample_rate)); //assert(aac.channel_configuration == channel_count); if (0) { // RFC 6416 // In the presence of SBR, the sampling rates for the core encoder/ // decoder and the SBR tool are different in most cases. Therefore, // this parameter SHALL NOT be considered as the definitive sampling rate. m->frequency = sample_rate; m->payload = RTP_PAYLOAD_LATM; snprintf(m->name, sizeof(m->name), "%s", "MP4A-LATM"); n = sdp_aac_latm(buffer, sizeof(buffer), "RTP/AVP", 0, RTP_PAYLOAD_LATM, sample_rate, channel_count, extra, bytes); } else { // RFC 3640 3.3.1. General (p21) // a=rtpmap: /[/ ] // For audio streams, specifies the number of audio channels // streamType: AudioStream // When using SDP, the clock rate of the RTP time stamp MUST be expressed using the "rtpmap" attribute. // If an MPEG-4 audio stream is transported, the rate SHOULD be set to the same value as the sampling rate of the audio stream. // If an MPEG-4 video stream transported, it is RECOMMENDED that the rate be set to 90 kHz. m->frequency = sample_rate; m->payload = RTP_PAYLOAD_MP4A; snprintf(m->name, sizeof(m->name), "%s", "MPEG4-GENERIC"); n = sdp_aac_generic(buffer, sizeof(buffer), "RTP/AVP", 0, RTP_PAYLOAD_MP4A, sample_rate, channel_count, extra, bytes); } } else if (MOV_OBJECT_OPUS == object) { // RFC7587 RTP Payload Format for the Opus Speech and Audio Codec m->frequency = sample_rate; m->payload = RTP_PAYLOAD_OPUS; snprintf(m->name, sizeof(m->name), "%s", "opus"); n = sdp_opus(buffer, sizeof(buffer), "RTP/AVP", 0, RTP_PAYLOAD_OPUS, sample_rate, channel_count, extra, bytes); } else if (MOV_OBJECT_G711u == object) { m->frequency = sample_rate; m->payload = RTP_PAYLOAD_PCMU; snprintf(m->name, sizeof(m->name), "%s", "PCMU"); n = sdp_g711u(buffer, sizeof(buffer), "RTP/AVP", 0); } else { assert(0); return; } struct rtp_payload_t rtpfunc = { FFFileSource::RTPAlloc, FFFileSource::RTPFree, FFFileSource::RTPPacket, }; m->packer = rtp_payload_encode_create(m->payload, m->name, (uint16_t)m->timestamp, m->ssrc, &rtpfunc, m); struct rtp_event_t event; event.on_rtcp = OnRTCPEvent; m->rtp = rtp_create(&event, self, m->ssrc, m->timestamp, m->frequency, m->bandwidth, 1); n += snprintf((char*)buffer + n, sizeof(buffer) - n, "a=control:track%d\n", m->track); self->m_sdp += (const char*)buffer; } void FFFileSource::OnRTCPEvent(const struct rtcp_msg_t* msg) { msg; } void FFFileSource::OnRTCPEvent(void* param, const struct rtcp_msg_t* msg) { FFFileSource *self = (FFFileSource *)param; self->OnRTCPEvent(msg); } int FFFileSource::SendBye() { char rtcp[1024] = { 0 }; for (int i = 0; i < m_count; i++) { struct media_t* m = &m_media[i]; size_t n = rtp_rtcp_bye(m->rtp, rtcp, sizeof(rtcp)); // send RTCP packet m->transport->Send(true, rtcp, n); } return 0; } int FFFileSource::SendRTCP(struct media_t* m, uint64_t clock) { // make sure have sent RTP packet int interval = rtp_rtcp_interval(m->rtp); if (0 == m->rtcp_clock || m->rtcp_clock + interval < clock) { char rtcp[1024] = { 0 }; size_t n = rtp_rtcp_report(m->rtp, rtcp, sizeof(rtcp)); // send RTCP packet m->transport->Send(true, rtcp, n); m->rtcp_clock = clock; } return 0; } void* FFFileSource::RTPAlloc(void* param, int bytes) { struct media_t* m = (struct media_t*)param; assert(bytes <= sizeof(m->packet)); return m->packet; } void FFFileSource::RTPFree(void* param, void *packet) { struct media_t* m = (struct media_t*)param; assert(m->packet == packet); } int FFFileSource::RTPPacket(void* param, const void *packet, int bytes, uint32_t /*timestamp*/, int /*flags*/) { struct media_t* m = (struct media_t*)param; assert(m->packet == packet); // Hack: Send an initial RTCP "SR" packet, before the initial RTP packet, // so that receivers will (likely) be able to get RTCP-synchronized presentation times immediately: rtp_onsend(m->rtp, packet, bytes/*, time*/); SendRTCP(m, system_time()); int r = m->transport->Send(false, packet, bytes); assert(r == (int)bytes); return 0; } #endif