Merge pull request #2700 from signalwire/mp4
[mod_mp4, mod_mp4v, mod_mp4v2] Remove from tree
This commit is contained in:
commit
c1eb76c828
|
@ -29,8 +29,6 @@ applications/mod_httapi
|
||||||
#applications/mod_lcr
|
#applications/mod_lcr
|
||||||
#applications/mod_memcache
|
#applications/mod_memcache
|
||||||
#applications/mod_mongo
|
#applications/mod_mongo
|
||||||
#applications/mod_mp4
|
|
||||||
#applications/mod_mp4v2
|
|
||||||
#applications/mod_nibblebill
|
#applications/mod_nibblebill
|
||||||
#applications/mod_oreka
|
#applications/mod_oreka
|
||||||
#applications/mod_osp
|
#applications/mod_osp
|
||||||
|
@ -69,7 +67,6 @@ codecs/mod_g729
|
||||||
codecs/mod_h26x
|
codecs/mod_h26x
|
||||||
#codecs/mod_ilbc
|
#codecs/mod_ilbc
|
||||||
#codecs/mod_isac
|
#codecs/mod_isac
|
||||||
#codecs/mod_mp4v
|
|
||||||
codecs/mod_opus
|
codecs/mod_opus
|
||||||
#codecs/mod_silk
|
#codecs/mod_silk
|
||||||
#codecs/mod_siren
|
#codecs/mod_siren
|
||||||
|
|
|
@ -29,7 +29,6 @@ applications/mod_http_cache
|
||||||
applications/mod_lcr
|
applications/mod_lcr
|
||||||
applications/mod_memcache
|
applications/mod_memcache
|
||||||
applications/mod_mongo
|
applications/mod_mongo
|
||||||
applications/mod_mp4
|
|
||||||
applications/mod_nibblebill
|
applications/mod_nibblebill
|
||||||
applications/mod_oreka
|
applications/mod_oreka
|
||||||
#applications/mod_osp
|
#applications/mod_osp
|
||||||
|
@ -67,7 +66,6 @@ codecs/mod_g729
|
||||||
codecs/mod_h26x
|
codecs/mod_h26x
|
||||||
codecs/mod_ilbc
|
codecs/mod_ilbc
|
||||||
codecs/mod_isac
|
codecs/mod_isac
|
||||||
codecs/mod_mp4v
|
|
||||||
codecs/mod_opus
|
codecs/mod_opus
|
||||||
codecs/mod_silk
|
codecs/mod_silk
|
||||||
codecs/mod_siren
|
codecs/mod_siren
|
||||||
|
|
1
ci.sh
1
ci.sh
|
@ -93,7 +93,6 @@ configure_freeswitch()
|
||||||
sed -i \
|
sed -i \
|
||||||
-e '/mod_ilbc/s/^/#/g' \
|
-e '/mod_ilbc/s/^/#/g' \
|
||||||
-e '/mod_isac/s/^/#/g' \
|
-e '/mod_isac/s/^/#/g' \
|
||||||
-e '/mod_mp4/s/^/#/g' \
|
|
||||||
-e '/mod_mongo/s/^/#/g' \
|
-e '/mod_mongo/s/^/#/g' \
|
||||||
-e '/mod_pocketsphinx/s/^/#/g' \
|
-e '/mod_pocketsphinx/s/^/#/g' \
|
||||||
-e '/mod_siren/s/^/#/g' \
|
-e '/mod_siren/s/^/#/g' \
|
||||||
|
|
|
@ -2119,8 +2119,6 @@ AC_CONFIG_FILES([Makefile
|
||||||
src/mod/applications/mod_limit/Makefile
|
src/mod/applications/mod_limit/Makefile
|
||||||
src/mod/applications/mod_memcache/Makefile
|
src/mod/applications/mod_memcache/Makefile
|
||||||
src/mod/applications/mod_mongo/Makefile
|
src/mod/applications/mod_mongo/Makefile
|
||||||
src/mod/applications/mod_mp4/Makefile
|
|
||||||
src/mod/applications/mod_mp4v2/Makefile
|
|
||||||
src/mod/applications/mod_nibblebill/Makefile
|
src/mod/applications/mod_nibblebill/Makefile
|
||||||
src/mod/applications/mod_oreka/Makefile
|
src/mod/applications/mod_oreka/Makefile
|
||||||
src/mod/applications/mod_osp/Makefile
|
src/mod/applications/mod_osp/Makefile
|
||||||
|
@ -2160,7 +2158,6 @@ AC_CONFIG_FILES([Makefile
|
||||||
src/mod/codecs/mod_h26x/Makefile
|
src/mod/codecs/mod_h26x/Makefile
|
||||||
src/mod/codecs/mod_ilbc/Makefile
|
src/mod/codecs/mod_ilbc/Makefile
|
||||||
src/mod/codecs/mod_isac/Makefile
|
src/mod/codecs/mod_isac/Makefile
|
||||||
src/mod/codecs/mod_mp4v/Makefile
|
|
||||||
src/mod/codecs/mod_opus/Makefile
|
src/mod/codecs/mod_opus/Makefile
|
||||||
src/mod/codecs/mod_openh264/Makefile
|
src/mod/codecs/mod_openh264/Makefile
|
||||||
src/mod/codecs/mod_silk/Makefile
|
src/mod/codecs/mod_silk/Makefile
|
||||||
|
|
|
@ -35,8 +35,6 @@ supported_distros="$supported_debian_distros $supported_ubuntu_distros"
|
||||||
avoid_mods=(
|
avoid_mods=(
|
||||||
applications/mod_limit
|
applications/mod_limit
|
||||||
applications/mod_mongo
|
applications/mod_mongo
|
||||||
applications/mod_mp4
|
|
||||||
applications/mod_mp4v2
|
|
||||||
applications/mod_osp
|
applications/mod_osp
|
||||||
applications/mod_rad_auth
|
applications/mod_rad_auth
|
||||||
applications/mod_skel
|
applications/mod_skel
|
||||||
|
@ -712,7 +710,6 @@ Depends: \${misc:Depends}, freeswitch (= \${binary:Version}),
|
||||||
freeswitch-mod-g729 (= \${binary:Version}),
|
freeswitch-mod-g729 (= \${binary:Version}),
|
||||||
freeswitch-mod-h26x (= \${binary:Version}),
|
freeswitch-mod-h26x (= \${binary:Version}),
|
||||||
freeswitch-mod-isac (= \${binary:Version}),
|
freeswitch-mod-isac (= \${binary:Version}),
|
||||||
freeswitch-mod-mp4v (= \${binary:Version}),
|
|
||||||
freeswitch-mod-opus (= \${binary:Version}),
|
freeswitch-mod-opus (= \${binary:Version}),
|
||||||
freeswitch-mod-silk (= \${binary:Version}),
|
freeswitch-mod-silk (= \${binary:Version}),
|
||||||
freeswitch-mod-spandsp (= \${binary:Version}),
|
freeswitch-mod-spandsp (= \${binary:Version}),
|
||||||
|
@ -738,7 +735,6 @@ Depends: \${misc:Depends}, freeswitch (= \${binary:Version}),
|
||||||
freeswitch-mod-g729-dbg (= \${binary:Version}),
|
freeswitch-mod-g729-dbg (= \${binary:Version}),
|
||||||
freeswitch-mod-h26x-dbg (= \${binary:Version}),
|
freeswitch-mod-h26x-dbg (= \${binary:Version}),
|
||||||
freeswitch-mod-isac-dbg (= \${binary:Version}),
|
freeswitch-mod-isac-dbg (= \${binary:Version}),
|
||||||
freeswitch-mod-mp4v-dbg (= \${binary:Version}),
|
|
||||||
freeswitch-mod-opus-dbg (= \${binary:Version}),
|
freeswitch-mod-opus-dbg (= \${binary:Version}),
|
||||||
freeswitch-mod-silk-dbg (= \${binary:Version}),
|
freeswitch-mod-silk-dbg (= \${binary:Version}),
|
||||||
freeswitch-mod-spandsp-dbg (= \${binary:Version}),
|
freeswitch-mod-spandsp-dbg (= \${binary:Version}),
|
||||||
|
|
|
@ -149,16 +149,6 @@ Description: MongoDB
|
||||||
This module provides an interface to MongoDB.
|
This module provides an interface to MongoDB.
|
||||||
Build-Depends: libmongoc-dev
|
Build-Depends: libmongoc-dev
|
||||||
|
|
||||||
Module: applications/mod_mp4
|
|
||||||
Section: contrib/comm
|
|
||||||
Description: MP4 video support
|
|
||||||
This module adds support for MP4 video playback.
|
|
||||||
Build-Depends: libmp4v2-dev
|
|
||||||
|
|
||||||
Module: applications/mod_mp4v2
|
|
||||||
Description: Adds mod_mp4v2
|
|
||||||
Adds mod_mp4v2.
|
|
||||||
|
|
||||||
Module: applications/mod_nibblebill
|
Module: applications/mod_nibblebill
|
||||||
Description: Nibblebill
|
Description: Nibblebill
|
||||||
This module allows for real-time accounting of a cash balance and
|
This module allows for real-time accounting of a cash balance and
|
||||||
|
@ -333,10 +323,6 @@ Module: codecs/mod_isac
|
||||||
Description: mod_isac
|
Description: mod_isac
|
||||||
Adds mod_isac.
|
Adds mod_isac.
|
||||||
|
|
||||||
Module: codecs/mod_mp4v
|
|
||||||
Description: mod_mp4v
|
|
||||||
Adds mod_mp4v.
|
|
||||||
|
|
||||||
Module: codecs/mod_openh264
|
Module: codecs/mod_openh264
|
||||||
Description: Adds mod_openh264
|
Description: Adds mod_openh264
|
||||||
Adds mod_openh264.
|
Adds mod_openh264.
|
||||||
|
|
|
@ -722,14 +722,6 @@ Requires: %{name} = %{version}-%{release}
|
||||||
%description codec-vpx
|
%description codec-vpx
|
||||||
iSAC Codec support for FreeSWITCH open source telephony platform
|
iSAC Codec support for FreeSWITCH open source telephony platform
|
||||||
|
|
||||||
%package codec-mp4v
|
|
||||||
Summary: MP4V Video Codec support for FreeSWITCH open source telephony platform
|
|
||||||
Group: System/Libraries
|
|
||||||
Requires: %{name} = %{version}-%{release}
|
|
||||||
|
|
||||||
%description codec-mp4v
|
|
||||||
MP4V Video Codec support for FreeSWITCH open source telephony platform
|
|
||||||
|
|
||||||
%package codec-opus
|
%package codec-opus
|
||||||
Summary: Opus Codec support for FreeSWITCH open source telephony platform
|
Summary: Opus Codec support for FreeSWITCH open source telephony platform
|
||||||
Group: System/Libraries
|
Group: System/Libraries
|
||||||
|
@ -1351,7 +1343,7 @@ ASR_TTS_MODULES="asr_tts/mod_flite asr_tts/mod_pocketsphinx asr_tts/mod_tts_comm
|
||||||
#
|
#
|
||||||
######################################################################################################################
|
######################################################################################################################
|
||||||
CODECS_MODULES="codecs/mod_amr codecs/mod_amrwb codecs/mod_bv codecs/mod_codec2 codecs/mod_g723_1 \
|
CODECS_MODULES="codecs/mod_amr codecs/mod_amrwb codecs/mod_bv codecs/mod_codec2 codecs/mod_g723_1 \
|
||||||
codecs/mod_g729 codecs/mod_h26x codecs/mod_ilbc codecs/mod_isac codecs/mod_mp4v codecs/mod_opus codecs/mod_silk \
|
codecs/mod_g729 codecs/mod_h26x codecs/mod_ilbc codecs/mod_isac codecs/mod_opus codecs/mod_silk \
|
||||||
codecs/mod_siren codecs/mod_theora"
|
codecs/mod_siren codecs/mod_theora"
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@ -2075,9 +2067,6 @@ fi
|
||||||
%files codec-isac
|
%files codec-isac
|
||||||
%{MODINSTDIR}/mod_isac.so*
|
%{MODINSTDIR}/mod_isac.so*
|
||||||
|
|
||||||
%files codec-mp4v
|
|
||||||
%{MODINSTDIR}/mod_mp4v.so*
|
|
||||||
|
|
||||||
%files codec-opus
|
%files codec-opus
|
||||||
%{MODINSTDIR}/mod_opus.so*
|
%{MODINSTDIR}/mod_opus.so*
|
||||||
%config(noreplace) %attr(0640, freeswitch, daemon) %{sysconfdir}/autoload_configs/opus.conf.xml
|
%config(noreplace) %attr(0640, freeswitch, daemon) %{sysconfdir}/autoload_configs/opus.conf.xml
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
include $(top_srcdir)/build/modmake.rulesam
|
|
||||||
MODNAME=mod_mp4
|
|
||||||
|
|
||||||
mod_LTLIBRARIES = mod_mp4.la
|
|
||||||
mod_mp4_la_SOURCES = mod_mp4.cpp mp4_helper.cpp
|
|
||||||
mod_mp4_la_CFLAGS = $(AM_CFLAGS)
|
|
||||||
mod_mp4_la_LIBADD = $(switch_builddir)/libfreeswitch.la
|
|
||||||
mod_mp4_la_LDFLAGS = -avoid-version -module -no-undefined -shared -lmp4v2
|
|
|
@ -1,59 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
The contents of this file are subject to the Mozilla Public License
|
|
||||||
Version 1.1 (the "License"); you may not use this file except in
|
|
||||||
compliance with the License. You may obtain a copy of the License at
|
|
||||||
http://www.mozilla.org/MPL/
|
|
||||||
|
|
||||||
Software distributed under the License is distributed on an "AS IS"
|
|
||||||
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing rights and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
The Original Code is MP4 Helper Library to Freeswitch MP4 module.
|
|
||||||
|
|
||||||
The Initial Developer of the Original Code is
|
|
||||||
Paulo Rogério Panhoto <paulo@voicetechnology.com.br>.
|
|
||||||
Portions created by the Initial Developer are Copyright (C)
|
|
||||||
the Initial Developer. All Rights Reserved.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef EXCEPTION_HPP_
|
|
||||||
#define EXCEPTION_HPP_
|
|
||||||
|
|
||||||
#include <exception>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class Exception: public std::exception {
|
|
||||||
public:
|
|
||||||
Exception()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Exception(const std::string & message): message_(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Exception(const std::exception & e): message_(e.what())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Exception(const Exception & e): message_(e.message_)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~Exception() throw()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
const char * what() const throw()
|
|
||||||
{
|
|
||||||
return message_.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string message_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,550 +0,0 @@
|
||||||
/*
|
|
||||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
|
||||||
* Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
|
|
||||||
*
|
|
||||||
* Version: MPL 1.1
|
|
||||||
*
|
|
||||||
* The contents of this file are subject to the Mozilla Public License Version
|
|
||||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
* http://www.mozilla.org/MPL/
|
|
||||||
*
|
|
||||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
||||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
||||||
* for the specific language governing rights and limitations under the
|
|
||||||
* License.
|
|
||||||
*
|
|
||||||
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
|
||||||
*
|
|
||||||
* The Initial Developer of the Original Code is
|
|
||||||
* Paulo Rogério Panhoto <paulo@voicetechnology.com.br>
|
|
||||||
* Portions created by the Initial Developer are Copyright (C)
|
|
||||||
* the Initial Developer. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Contributor(s):
|
|
||||||
*
|
|
||||||
* mod_mp4 -- MP4 File Format support for video apps.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include "mp4_helper.hpp"
|
|
||||||
#include "exception.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef min
|
|
||||||
#define min(x, y) ((x) < (y) ? (x) : (y))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SWITCH_MODULE_LOAD_FUNCTION(mod_mp4_load);
|
|
||||||
SWITCH_MODULE_DEFINITION(mod_mp4, mod_mp4_load, NULL, NULL);
|
|
||||||
|
|
||||||
#define VID_BIT (1 << 31)
|
|
||||||
#define VERSION 4201
|
|
||||||
|
|
||||||
#ifdef MP4_RECORD
|
|
||||||
struct file_header {
|
|
||||||
int32_t version;
|
|
||||||
char video_codec_name[32];
|
|
||||||
char video_fmtp[128];
|
|
||||||
uint32_t audio_rate;
|
|
||||||
uint32_t audio_ptime;
|
|
||||||
switch_time_t created;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct record_helper {
|
|
||||||
switch_core_session_t *session;
|
|
||||||
switch_mutex_t *mutex;
|
|
||||||
int fd;
|
|
||||||
int up;
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct AVParams {
|
|
||||||
switch_core_session_t * session;
|
|
||||||
switch_channel_t * channel;
|
|
||||||
switch_timer_t * timer;
|
|
||||||
switch_frame_t * frame;
|
|
||||||
switch_mutex_t * mutex;
|
|
||||||
bool video;
|
|
||||||
switch_payload_t pt;
|
|
||||||
MP4::Context * vc;
|
|
||||||
bool done;
|
|
||||||
bool * quit;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void *SWITCH_THREAD_FUNC record_video_thread(switch_thread_t *thread, void *obj)
|
|
||||||
{
|
|
||||||
#ifdef MP4_RECORD
|
|
||||||
record_helper *eh = reinterpret_cast<record_helper *>(obj);
|
|
||||||
switch_core_session_t *session = eh->session;
|
|
||||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
|
||||||
switch_status_t status;
|
|
||||||
switch_frame_t *read_frame;
|
|
||||||
int bytes;
|
|
||||||
|
|
||||||
eh->up = 1;
|
|
||||||
while (switch_channel_ready(channel)) {
|
|
||||||
status = switch_core_session_read_video_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0);
|
|
||||||
|
|
||||||
if (!SWITCH_READ_ACCEPTABLE(status)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (switch_test_flag(read_frame, SFF_CNG)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes = read_frame->packetlen | VID_BIT;
|
|
||||||
|
|
||||||
switch_mutex_lock(eh->mutex);
|
|
||||||
|
|
||||||
if (write(eh->fd, &bytes, sizeof(bytes)) != (int) sizeof(bytes)) {
|
|
||||||
switch_mutex_unlock(eh->mutex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (write(eh->fd, read_frame->packet, read_frame->packetlen) != (int) read_frame->packetlen) {
|
|
||||||
switch_mutex_unlock(eh->mutex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_mutex_unlock(eh->mutex);
|
|
||||||
|
|
||||||
switch_core_session_write_video_frame(session, read_frame, SWITCH_IO_FLAG_NONE, 0);
|
|
||||||
}
|
|
||||||
eh->up = 0;
|
|
||||||
#endif
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
SWITCH_STANDARD_APP(record_mp4_function)
|
|
||||||
{
|
|
||||||
#ifdef MP4_RECORD
|
|
||||||
switch_status_t status;
|
|
||||||
switch_frame_t *read_frame;
|
|
||||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
|
||||||
struct record_helper eh = { 0 };
|
|
||||||
switch_thread_t *thread;
|
|
||||||
switch_threadattr_t *thd_attr = NULL;
|
|
||||||
int fd;
|
|
||||||
switch_mutex_t *mutex = NULL;
|
|
||||||
switch_codec_t codec, *vid_codec;
|
|
||||||
switch_codec_implementation_t read_impl = { };
|
|
||||||
int count = 0, sanity = 30;
|
|
||||||
|
|
||||||
switch_core_session_get_read_impl(session, &read_impl);
|
|
||||||
switch_channel_answer(channel);
|
|
||||||
|
|
||||||
|
|
||||||
while (switch_channel_up(channel) && !switch_channel_test_flag(channel, CF_VIDEO)) {
|
|
||||||
switch_yield(10000);
|
|
||||||
|
|
||||||
if (count) count--;
|
|
||||||
|
|
||||||
if (count == 0) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "%s waiting for video.\n", switch_channel_get_name(channel));
|
|
||||||
count = 100;
|
|
||||||
if (!--sanity) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "%s timeout waiting for video.\n",
|
|
||||||
switch_channel_get_name(channel));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!switch_channel_ready(channel)) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "%s not ready.\n", switch_channel_get_name(channel));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
if ((fd = open((char *) data, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR)) < 0) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Error opening file %s\n", (char *) data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
MP4::Context ctx(reinterpret_cast<char*>(data), true);
|
|
||||||
|
|
||||||
if (switch_core_codec_init(&codec,
|
|
||||||
"L16",
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
read_impl.samples_per_second,
|
|
||||||
read_impl.microseconds_per_packet / 1000,
|
|
||||||
1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
|
|
||||||
NULL, switch_core_session_get_pool(session)) == SWITCH_STATUS_SUCCESS)
|
|
||||||
{
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Audio Codec Activation Success\n");
|
|
||||||
} else {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Audio Codec Activation Fail\n");
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_core_session_set_read_codec(session, &codec);
|
|
||||||
|
|
||||||
if (switch_channel_test_flag(channel, CF_VIDEO)) {
|
|
||||||
struct file_header h;
|
|
||||||
memset(&h, 0, sizeof(h));
|
|
||||||
vid_codec = switch_core_session_get_video_read_codec(session);
|
|
||||||
|
|
||||||
h.version = VERSION;
|
|
||||||
h.created = switch_micro_time_now();
|
|
||||||
switch_set_string(h.video_codec_name, vid_codec->implementation->iananame);
|
|
||||||
if (vid_codec->fmtp_in) {
|
|
||||||
switch_set_string(h.video_fmtp, vid_codec->fmtp_in);
|
|
||||||
}
|
|
||||||
h.audio_rate = read_impl.samples_per_second;
|
|
||||||
h.audio_ptime = read_impl.microseconds_per_packet / 1000;
|
|
||||||
|
|
||||||
if (write(fd, &h, sizeof(h)) != sizeof(h)) {
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_mutex_init(&mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
|
|
||||||
eh.mutex = mutex;
|
|
||||||
eh.fd = fd;
|
|
||||||
eh.session = session;
|
|
||||||
switch_threadattr_create(&thd_attr, switch_core_session_get_pool(session));
|
|
||||||
switch_threadattr_detach_set(thd_attr, 1);
|
|
||||||
switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
|
|
||||||
switch_thread_create(&thread, thd_attr, record_video_thread, &eh, switch_core_session_get_pool(session));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
while (switch_channel_ready(channel)) {
|
|
||||||
|
|
||||||
status = switch_core_session_read_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0);
|
|
||||||
|
|
||||||
if (!SWITCH_READ_ACCEPTABLE(status)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (switch_test_flag(read_frame, SFF_CNG)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mutex) {
|
|
||||||
switch_mutex_lock(mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (write(fd, &read_frame->datalen, sizeof(read_frame->datalen)) != sizeof(read_frame->datalen)) {
|
|
||||||
if (mutex) {
|
|
||||||
switch_mutex_unlock(mutex);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (write(fd, read_frame->data, read_frame->datalen) != (int) read_frame->datalen) {
|
|
||||||
if (mutex) {
|
|
||||||
switch_mutex_unlock(mutex);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mutex) {
|
|
||||||
switch_mutex_unlock(mutex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
end:
|
|
||||||
|
|
||||||
if (eh.up) {
|
|
||||||
while (eh.up) {
|
|
||||||
switch_cond_next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_core_session_set_read_codec(session, NULL);
|
|
||||||
switch_core_codec_destroy(&codec);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *SWITCH_THREAD_FUNC play_video_function(switch_thread_t *thread, void *obj)
|
|
||||||
{
|
|
||||||
AVParams * pt = reinterpret_cast<AVParams*>(obj);
|
|
||||||
u_int next = 0, first = 0xffffffff;
|
|
||||||
uint64_t ts = 0, control = 0;
|
|
||||||
|
|
||||||
bool ok;
|
|
||||||
bool sent = true;
|
|
||||||
pt->done = false;
|
|
||||||
switch_time_t start = switch_time_now();
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pt->session), SWITCH_LOG_DEBUG, "Video thread Started\n");
|
|
||||||
while (!*pt->quit && switch_channel_ready(pt->channel)) {
|
|
||||||
if (pt->video) {
|
|
||||||
if (sent) {
|
|
||||||
switch_mutex_lock(pt->mutex);
|
|
||||||
pt->frame->packetlen = pt->frame->buflen;
|
|
||||||
ok = pt->vc->getVideoPacket(pt->frame->packet, pt->frame->packetlen, next);
|
|
||||||
switch_mutex_unlock(pt->mutex);
|
|
||||||
sent = false;
|
|
||||||
if (ok) {
|
|
||||||
switch_rtp_hdr_t *hdr = reinterpret_cast<switch_rtp_hdr_t *>(pt->frame->packet);
|
|
||||||
if(first == 0xffffffff) first = next;
|
|
||||||
next -= first;
|
|
||||||
control = next * 90000LL / pt->vc->videoTrack().track.clock;
|
|
||||||
control -= first;
|
|
||||||
hdr->ts = htonl(control);
|
|
||||||
control = control * 1000 / 90;
|
|
||||||
if (pt->pt)
|
|
||||||
hdr->pt = pt->pt;
|
|
||||||
} else break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ts = switch_time_now() - start;
|
|
||||||
int64_t wait = control > ts ? (control - ts) : 0;
|
|
||||||
|
|
||||||
if (wait > 0) {
|
|
||||||
switch_cond_next();
|
|
||||||
// wait the time for the next Video frame
|
|
||||||
switch_sleep(wait);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (switch_channel_test_flag(pt->channel, CF_VIDEO)) {
|
|
||||||
switch_byte_t *data = (switch_byte_t *) pt->frame->packet;
|
|
||||||
|
|
||||||
pt->frame->data = data + 12;
|
|
||||||
pt->frame->datalen = pt->frame->packetlen - 12;
|
|
||||||
switch_core_session_write_video_frame(pt->session, pt->frame, SWITCH_IO_FLAG_NONE, 0);
|
|
||||||
sent = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pt->session), SWITCH_LOG_DEBUG, "Video thread ended\n");
|
|
||||||
pt->done = true;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *SWITCH_THREAD_FUNC play_audio_function(switch_thread_t *thread, void *obj)
|
|
||||||
{
|
|
||||||
AVParams * pt = reinterpret_cast<AVParams*>(obj);
|
|
||||||
u_int next = 0, first = 0xffffffff;
|
|
||||||
uint64_t ts = 0, control = 0;
|
|
||||||
|
|
||||||
bool ok;
|
|
||||||
bool sent = true;
|
|
||||||
switch_dtmf_t dtmf = {0};
|
|
||||||
pt->done = false;
|
|
||||||
switch_frame_t * read_frame;
|
|
||||||
|
|
||||||
while (!*pt->quit && switch_channel_ready(pt->channel)) {
|
|
||||||
// event processing.
|
|
||||||
// -- SEE switch_ivr_play_say.c:1231 && mod_dptools.c:1428 && mod_dptools.c:1919
|
|
||||||
switch_core_session_read_frame(pt->session, &read_frame, SWITCH_IO_FLAG_SINGLE_READ, 0);
|
|
||||||
|
|
||||||
if (switch_channel_test_flag(pt->channel, CF_BREAK)) {
|
|
||||||
switch_channel_clear_flag(pt->channel, CF_BREAK);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_ivr_parse_all_events(pt->session);
|
|
||||||
|
|
||||||
if (switch_channel_has_dtmf(pt->channel)) {
|
|
||||||
switch_channel_dequeue_dtmf(pt->channel, &dtmf);
|
|
||||||
const char * terminators = switch_channel_get_variable(pt->channel, SWITCH_PLAYBACK_TERMINATORS_VARIABLE);
|
|
||||||
if (terminators && !strcasecmp(terminators, "none")) terminators = NULL;
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pt->session), SWITCH_LOG_DEBUG, "Digit %c\n", dtmf.digit);
|
|
||||||
if (terminators && strchr(terminators, dtmf.digit)) {
|
|
||||||
std::string digit(&dtmf.digit, 0, 1);
|
|
||||||
switch_channel_set_variable(pt->channel, SWITCH_PLAYBACK_TERMINATOR_USED, digit.c_str());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_mutex_lock(pt->mutex);
|
|
||||||
pt->frame->datalen = pt->frame->buflen;
|
|
||||||
ok = pt->vc->getAudioPacket(pt->frame->data, pt->frame->datalen, next);
|
|
||||||
switch_mutex_unlock(pt->mutex);
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
if (pt->frame->datalen > (int) pt->frame->buflen)
|
|
||||||
pt->frame->datalen = pt->frame->buflen;
|
|
||||||
|
|
||||||
switch_core_session_write_frame(pt->session, pt->frame, SWITCH_IO_FLAG_NONE, 0);
|
|
||||||
switch_core_timer_next(pt->timer);
|
|
||||||
}
|
|
||||||
else break;
|
|
||||||
}
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pt->session), SWITCH_LOG_DEBUG, "Audio done\n");
|
|
||||||
*pt->quit = pt->done = true;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
SWITCH_STANDARD_APP(play_mp4_function)
|
|
||||||
{
|
|
||||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
|
||||||
switch_frame_t write_frame = { 0 }, vid_frame = {0};
|
|
||||||
switch_codec_t codec = { 0 }, vid_codec = {0}, *read_vid_codec;
|
|
||||||
unsigned char *aud_buffer;
|
|
||||||
unsigned char *vid_buffer;
|
|
||||||
switch_timer_t timer = { 0 };
|
|
||||||
switch_codec_implementation_t read_impl = {};
|
|
||||||
bool done = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
MP4::Context vc((char *) data);
|
|
||||||
|
|
||||||
switch_payload_t pt = 0;
|
|
||||||
|
|
||||||
switch_core_session_get_read_impl(session, &read_impl);
|
|
||||||
|
|
||||||
aud_buffer = (unsigned char *) switch_core_session_alloc(session, SWITCH_RECOMMENDED_BUFFER_SIZE);
|
|
||||||
vid_buffer = (unsigned char *) switch_core_session_alloc(session, SWITCH_RECOMMENDED_BUFFER_SIZE);
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (!vc.isOpen())
|
|
||||||
{
|
|
||||||
char msgbuf[1024];
|
|
||||||
sprintf(msgbuf, "PLAYBACK ERROR (%s): FILE NOT FOUND.", (char*) data);
|
|
||||||
switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, msgbuf);
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Error opening file %s\n", (char *) data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!vc.isSupported())
|
|
||||||
{
|
|
||||||
char msgbuf[1024];
|
|
||||||
sprintf(msgbuf, "PLAYBACK ERROR (%s): UNSUPPORTED FORMAT OR FILE NOT HINTED.", (char*) data);
|
|
||||||
switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, msgbuf);
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT,
|
|
||||||
"Error reading track info. Maybe this file is not hinted.\n");
|
|
||||||
throw 1;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
switch_channel_set_variable(channel, "rtp_force_video_fmtp", vc.videoTrack().fmtp.c_str());
|
|
||||||
switch_channel_answer(channel);
|
|
||||||
|
|
||||||
if ((read_vid_codec = switch_core_session_get_video_read_codec(session))) {
|
|
||||||
pt = read_vid_codec->agreed_pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
write_frame.codec = &codec;
|
|
||||||
write_frame.data = aud_buffer;
|
|
||||||
write_frame.buflen = SWITCH_RECOMMENDED_BUFFER_SIZE;
|
|
||||||
|
|
||||||
vid_frame.codec = &vid_codec;
|
|
||||||
vid_frame.packet = vid_buffer;
|
|
||||||
vid_frame.data = vid_buffer + 12;
|
|
||||||
vid_frame.buflen = SWITCH_RECOMMENDED_BUFFER_SIZE - 12;
|
|
||||||
switch_set_flag((&vid_frame), SFF_RAW_RTP);
|
|
||||||
switch_set_flag((&vid_frame), SFF_PROXY_PACKET);
|
|
||||||
|
|
||||||
if (switch_core_timer_init(&timer, "soft", read_impl.microseconds_per_packet / 1000,
|
|
||||||
read_impl.samples_per_packet, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Timer Activation Fail\n");
|
|
||||||
throw 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (switch_core_codec_init(&codec,
|
|
||||||
vc.audioTrack().codecName,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
vc.audioTrack().clock,
|
|
||||||
vc.audioTrack().packetLength,
|
|
||||||
1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
|
|
||||||
NULL, switch_core_session_get_pool(session)) == SWITCH_STATUS_SUCCESS) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Audio Codec Activation Success\n");
|
|
||||||
} else {
|
|
||||||
throw Exception("Audio Codec Activation Fail");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (switch_core_codec_init(&vid_codec,
|
|
||||||
vc.videoTrack().track.codecName,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
|
|
||||||
NULL, switch_core_session_get_pool(session)) == SWITCH_STATUS_SUCCESS) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Video Codec Activation Success\n");
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
throw Exception("Video Codec Activation Fail");
|
|
||||||
}
|
|
||||||
switch_core_session_set_read_codec(session, &codec);
|
|
||||||
|
|
||||||
AVParams vpt;
|
|
||||||
vpt.session = session;
|
|
||||||
vpt.channel = channel;
|
|
||||||
vpt.frame = &vid_frame;
|
|
||||||
vpt.timer = &timer;
|
|
||||||
vpt.video = true;
|
|
||||||
vpt.pt = pt;
|
|
||||||
vpt.vc = &vc;
|
|
||||||
switch_mutex_init(&vpt.mutex, SWITCH_MUTEX_DEFAULT, switch_core_session_get_pool(session));
|
|
||||||
vpt.quit = &done;
|
|
||||||
|
|
||||||
switch_threadattr_t * thd_attr;
|
|
||||||
switch_threadattr_create(&thd_attr, switch_core_session_get_pool(session));
|
|
||||||
switch_threadattr_detach_set(thd_attr, 1);
|
|
||||||
switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
|
|
||||||
switch_thread_t *thread;
|
|
||||||
switch_thread_create(&thread, thd_attr, play_video_function, (void*)&vpt, switch_core_session_get_pool(session));
|
|
||||||
|
|
||||||
AVParams apt;
|
|
||||||
apt.session = session;
|
|
||||||
apt.channel = channel;
|
|
||||||
apt.frame = &write_frame;
|
|
||||||
apt.timer = &timer;
|
|
||||||
apt.video = false;
|
|
||||||
apt.vc = &vc;
|
|
||||||
apt.mutex = vpt.mutex;
|
|
||||||
apt.quit = &done;
|
|
||||||
play_audio_function(NULL, &apt);
|
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Waiting for video thread to join.\n");
|
|
||||||
while (!vpt.done) {
|
|
||||||
switch_cond_next();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "FILE PLAYED");
|
|
||||||
} catch(const std::exception & e)
|
|
||||||
{
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "%s\n", e.what());
|
|
||||||
switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE,
|
|
||||||
(std::string("PLAYBACK_FAILED - ") + e.what()).c_str());
|
|
||||||
}catch(...)
|
|
||||||
{
|
|
||||||
switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "PLAYBACK_FAILED - See FS logs for detail.");
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Exception caught.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "All done.\n");
|
|
||||||
if (timer.interval) switch_core_timer_destroy(&timer);
|
|
||||||
|
|
||||||
switch_core_session_set_read_codec(session, NULL);
|
|
||||||
|
|
||||||
if (switch_core_codec_ready(&codec)) switch_core_codec_destroy(&codec);
|
|
||||||
|
|
||||||
if (switch_core_codec_ready(&vid_codec)) switch_core_codec_destroy(&vid_codec);
|
|
||||||
}
|
|
||||||
|
|
||||||
SWITCH_MODULE_LOAD_FUNCTION(mod_mp4_load)
|
|
||||||
{
|
|
||||||
switch_application_interface_t *app_interface;
|
|
||||||
|
|
||||||
/* connect my internal structure to the blank pointer passed to me */
|
|
||||||
*module_interface = switch_loadable_module_create_module_interface(pool, modname);
|
|
||||||
|
|
||||||
SWITCH_ADD_APP(app_interface, "play_mp4", "play an MP4 file", "play an MP4 file", play_mp4_function, "<file>", SAF_NONE);
|
|
||||||
//SWITCH_ADD_APP(app_interface, "record_mp4", "record an MP4 file", "record an MP4 file", record_mp4_function, "<file>", SAF_NONE);
|
|
||||||
|
|
||||||
/* indicate that the module should continue to be loaded */
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* For Emacs:
|
|
||||||
* Local Variables:
|
|
||||||
* mode:c
|
|
||||||
* indent-tabs-mode:t
|
|
||||||
* tab-width:4
|
|
||||||
* c-basic-offset:4
|
|
||||||
* End:
|
|
||||||
* For VIM:
|
|
||||||
* vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
|
|
||||||
*/
|
|
|
@ -1,136 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
The contents of this file are subject to the Mozilla Public License
|
|
||||||
Version 1.1 (the "License"); you may not use this file except in
|
|
||||||
compliance with the License. You may obtain a copy of the License at
|
|
||||||
http://www.mozilla.org/MPL/
|
|
||||||
|
|
||||||
Software distributed under the License is distributed on an "AS IS"
|
|
||||||
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing rights and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
The Original Code is MP4 Helper Library to the Freeswitch MP4 Module.
|
|
||||||
|
|
||||||
The Initial Developer of the Original Code is
|
|
||||||
Paulo Rogério Panhoto <paulo@voicetechnology.com.br>.
|
|
||||||
Portions created by the Initial Developer are Copyright (C)
|
|
||||||
the Initial Developer. All Rights Reserved.
|
|
||||||
|
|
||||||
Contributors:
|
|
||||||
|
|
||||||
Seven Du <dujinfang@gmail.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include "mp4_helper.hpp"
|
|
||||||
|
|
||||||
namespace MP4
|
|
||||||
{
|
|
||||||
|
|
||||||
Context::Context(const char * file, bool newFile)
|
|
||||||
{
|
|
||||||
if(newFile) create(file);
|
|
||||||
else open(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
Context::~Context()
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Context::open(const char * file)
|
|
||||||
{
|
|
||||||
fh = MP4Read(file);
|
|
||||||
if (fh == MP4_INVALID_FILE_HANDLE) throw Exception(file, "Open failed");
|
|
||||||
getTracks(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Context::create(const char * file)
|
|
||||||
{
|
|
||||||
fh = MP4Create(file);
|
|
||||||
if (fh == MP4_INVALID_FILE_HANDLE) throw Exception(file, "Create file failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Context::close()
|
|
||||||
{
|
|
||||||
if (!isOpen()) return;
|
|
||||||
MP4Close(fh);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Context::getTracks(const char * file)
|
|
||||||
{
|
|
||||||
int i = 0;
|
|
||||||
bool audioTrack = false, videoTrack = false;
|
|
||||||
|
|
||||||
if (!isOpen()) throw Exception(file, "File is closed.");
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
TrackProperties track;
|
|
||||||
if((track.hint = MP4FindTrackId(fh, i++, MP4_HINT_TRACK_TYPE, 0)) == MP4_INVALID_TRACK_ID) break;
|
|
||||||
|
|
||||||
MP4GetHintTrackRtpPayload(fh, track.hint, &track.codecName, &track.payload, NULL, NULL);
|
|
||||||
|
|
||||||
track.track = MP4GetHintTrackReferenceTrackId(fh, track.hint);
|
|
||||||
if(track.track == MP4_INVALID_TRACK_ID) continue;
|
|
||||||
track.clock = MP4GetTrackTimeScale(fh, track.hint);
|
|
||||||
|
|
||||||
if (!strcmp(MP4GetTrackType(fh, track.track), MP4_AUDIO_TRACK_TYPE)) {
|
|
||||||
audioTrack = true;
|
|
||||||
|
|
||||||
if(!strncmp(track.codecName, "PCM", 3))
|
|
||||||
track.packetLength = 20;
|
|
||||||
else
|
|
||||||
track.packetLength = track.clock = 0;
|
|
||||||
|
|
||||||
audio = track;
|
|
||||||
} else if (!strcmp(MP4GetTrackType(fh, track.track), MP4_VIDEO_TRACK_TYPE)) {
|
|
||||||
videoTrack = true;
|
|
||||||
|
|
||||||
const char * sdp = MP4GetHintTrackSdp(fh, track.hint);
|
|
||||||
const char * fmtp = strstr(sdp, "fmtp");
|
|
||||||
|
|
||||||
if (fmtp) {
|
|
||||||
// finds beginning of 'fmtp' value;
|
|
||||||
for(fmtp += 5; *fmtp != ' '; ++fmtp);
|
|
||||||
++fmtp;
|
|
||||||
|
|
||||||
const char * eol = fmtp;
|
|
||||||
for(;*eol != '\r' && *eol != '\n'; ++eol);
|
|
||||||
video.fmtp = std::string(fmtp, eol);
|
|
||||||
}
|
|
||||||
video.track = track;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!audioTrack || !videoTrack) throw Exception(file, "Missing audio/video track.");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Context::getVideoPacket(void * buffer, u_int & size, u_int & ts)
|
|
||||||
{
|
|
||||||
return getPacket(video.track.hint, video.track.runtime, true, buffer, size, ts);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Context::getAudioPacket(void * buffer, u_int & size, u_int & ts)
|
|
||||||
{
|
|
||||||
return getPacket(audio.hint, audio.runtime, false, buffer, size, ts);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Context::getPacket(MP4TrackId hint, RuntimeProperties & rt,
|
|
||||||
bool header, void * buffer, u_int & size, u_int & ts)
|
|
||||||
{
|
|
||||||
if (rt.frame == 0 || rt.packet == rt.packetsPerFrame) {
|
|
||||||
++rt.frame;
|
|
||||||
if(!MP4ReadRtpHint(fh, hint, rt.frame, &rt.packetsPerFrame))
|
|
||||||
return false;
|
|
||||||
rt.packet = 0;
|
|
||||||
rt.last_frame = MP4GetSampleTime(fh, hint, rt.frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
ts = rt.last_frame;
|
|
||||||
if (!MP4ReadRtpPacket(fh, hint, rt.packet, (uint8_t **) &buffer, &size, 0, header, true)) return false;
|
|
||||||
++rt.packet;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
The contents of this file are subject to the Mozilla Public License
|
|
||||||
Version 1.1 (the "License"); you may not use this file except in
|
|
||||||
compliance with the License. You may obtain a copy of the License at
|
|
||||||
http://www.mozilla.org/MPL/
|
|
||||||
|
|
||||||
Software distributed under the License is distributed on an "AS IS"
|
|
||||||
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing rights and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
The Original Code is MP4 Helper Library to Freeswitch MP4 module.
|
|
||||||
|
|
||||||
The Initial Developer of the Original Code is
|
|
||||||
Paulo Rogério Panhoto <paulo@voicetechnology.com.br>.
|
|
||||||
Portions created by the Initial Developer are Copyright (C)
|
|
||||||
the Initial Developer. All Rights Reserved.
|
|
||||||
|
|
||||||
Contributor(s):
|
|
||||||
|
|
||||||
Seven Du <dujinfang@gmail.com>
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef MP4_HELPER_HPP_
|
|
||||||
#define MP4_HELPER_HPP_
|
|
||||||
|
|
||||||
#include <mp4v2/mp4v2.h>
|
|
||||||
#include <string>
|
|
||||||
#include <exception>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
typedef unsigned int u_int;
|
|
||||||
|
|
||||||
namespace MP4
|
|
||||||
{
|
|
||||||
class Exception: public std::exception {
|
|
||||||
public:
|
|
||||||
Exception(const std::string & file, const std::string & error)
|
|
||||||
: description_(file + ':' + error)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
const char * what() const throw()
|
|
||||||
{
|
|
||||||
return description_.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
~Exception() throw()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string description_;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeProperties {
|
|
||||||
uint32_t frame; // sampleID
|
|
||||||
uint16_t packetsPerFrame;
|
|
||||||
uint16_t packet; // packetID
|
|
||||||
uint32_t last_frame; // timestamp
|
|
||||||
|
|
||||||
RuntimeProperties(): frame(0), packetsPerFrame(0), packet(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct TrackProperties {
|
|
||||||
MP4TrackId hint;
|
|
||||||
MP4TrackId track;
|
|
||||||
|
|
||||||
char * codecName;
|
|
||||||
uint8_t payload;
|
|
||||||
uint32_t clock;
|
|
||||||
uint32_t packetLength; // packet Length in time (ms)
|
|
||||||
|
|
||||||
RuntimeProperties runtime;
|
|
||||||
|
|
||||||
TrackProperties(): hint(MP4_INVALID_TRACK_ID), track(MP4_INVALID_TRACK_ID),
|
|
||||||
codecName(NULL), payload(0), clock(0), packetLength(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef TrackProperties AudioProperties;
|
|
||||||
|
|
||||||
struct VideoProperties {
|
|
||||||
TrackProperties track;
|
|
||||||
std::string fmtp;
|
|
||||||
|
|
||||||
VideoProperties()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
VideoProperties(const TrackProperties & rhs): track(rhs)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Context {
|
|
||||||
public:
|
|
||||||
|
|
||||||
Context(const char * file, bool create = false);
|
|
||||||
~Context();
|
|
||||||
|
|
||||||
void open(const char * file);
|
|
||||||
|
|
||||||
void create(const char * file);
|
|
||||||
|
|
||||||
void close();
|
|
||||||
|
|
||||||
// returns: TRUE = has more data, FALSE = end-of-stream or failure
|
|
||||||
bool getVideoPacket(void * buffer, u_int & size, u_int & ts);
|
|
||||||
|
|
||||||
// returns: TRUE = has more data, FALSE = end-of-stream or failure
|
|
||||||
bool getAudioPacket(void * buffer, u_int & size, u_int & ts);
|
|
||||||
|
|
||||||
bool isOpen() const { return fh != MP4_INVALID_FILE_HANDLE; }
|
|
||||||
|
|
||||||
bool isSupported() const { return audio.track != MP4_INVALID_TRACK_ID && video.track.track != MP4_INVALID_TRACK_ID; }
|
|
||||||
|
|
||||||
const AudioProperties & audioTrack() const { return audio; }
|
|
||||||
|
|
||||||
const VideoProperties & videoTrack() const { return video; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
MP4FileHandle fh;
|
|
||||||
AudioProperties audio;
|
|
||||||
|
|
||||||
VideoProperties video;
|
|
||||||
|
|
||||||
// Prevent copy construction.
|
|
||||||
Context(const Context &);
|
|
||||||
|
|
||||||
bool getPacket(MP4TrackId hint, RuntimeProperties & rt,
|
|
||||||
bool header, void * buffer, u_int & size, u_int & ts);
|
|
||||||
|
|
||||||
void getTracks(const char * file);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1,8 +0,0 @@
|
||||||
include $(top_srcdir)/build/modmake.rulesam
|
|
||||||
MODNAME=mod_mp4v2
|
|
||||||
|
|
||||||
mod_LTLIBRARIES = mod_mp4v2.la
|
|
||||||
mod_mp4v2_la_SOURCES = mod_mp4v2.c
|
|
||||||
mod_mp4v2_la_CFLAGS = $(AM_CFLAGS)
|
|
||||||
mod_mp4v2_la_LIBADD = $(switch_builddir)/libfreeswitch.la
|
|
||||||
mod_mp4v2_la_LDFLAGS = -avoid-version -module -no-undefined -shared -lmp4v2
|
|
|
@ -1,772 +0,0 @@
|
||||||
/*
|
|
||||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
|
||||||
* Copyright (C) 2005-2015, Anthony Minessale II <anthm@freeswitch.org>
|
|
||||||
*
|
|
||||||
* Version: MPL 1.1
|
|
||||||
*
|
|
||||||
* The contents of this file are subject to the Mozilla Public License Version
|
|
||||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
* http://www.mozilla.org/MPL/
|
|
||||||
*
|
|
||||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
||||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
||||||
* for the specific language governing rights and limitations under the
|
|
||||||
* License.
|
|
||||||
*
|
|
||||||
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
|
||||||
*
|
|
||||||
* The Initial Developer of the Original Code is
|
|
||||||
* Seven Du <dujinfang@gmail.com>
|
|
||||||
* Portions created by the Initial Developer are Copyright (C)
|
|
||||||
* the Initial Developer. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Contributor(s):
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* mod_mp4v2 -- MP4 File Format for FreeSWITCH
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* status: For write, codec is hard coded to PCMU for audio and H264 for video
|
|
||||||
* tested with lib mp4v2-2.0.0
|
|
||||||
* Read from mp4 is not supported yet.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
#include <mp4v2/mp4v2.h>
|
|
||||||
|
|
||||||
#define TIMESCALE 1000
|
|
||||||
#define SampleLenFieldSize 4
|
|
||||||
#define PTIME 20
|
|
||||||
// #define HAS_SPS_PARSER
|
|
||||||
|
|
||||||
SWITCH_MODULE_LOAD_FUNCTION(mod_mp4v2_load);
|
|
||||||
SWITCH_MODULE_DEFINITION(mod_mp4v2, mod_mp4v2_load, NULL, NULL);
|
|
||||||
|
|
||||||
struct record_helper {
|
|
||||||
switch_core_session_t *session;
|
|
||||||
switch_mutex_t *mutex;
|
|
||||||
MP4FileHandle fd;
|
|
||||||
MP4TrackId video_track;
|
|
||||||
MP4TrackId audio_track;
|
|
||||||
switch_timer_t timer;
|
|
||||||
int up;
|
|
||||||
uint64_t last_pts;
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef HAS_SPS_PARSER
|
|
||||||
#include "bs.h"
|
|
||||||
static void parse_sps_video_size(uint8_t *sps_buffer, int len, int *width, int *height)
|
|
||||||
{
|
|
||||||
sps_t sps = { 0 };
|
|
||||||
bs_t b = { 0 };
|
|
||||||
|
|
||||||
bs_init(&b, sps_buffer, len);
|
|
||||||
read_sps(&sps, &b);
|
|
||||||
|
|
||||||
*width = ((sps.pic_width_in_mbs_minus1 +1)*16) - sps.frame_crop_left_offset*2 - sps.frame_crop_right_offset*2;
|
|
||||||
*height= ((2 - sps.frame_mbs_only_flag)* (sps.pic_height_in_map_units_minus1 +1) * 16) - (sps.frame_crop_top_offset * 2) - (sps.frame_crop_bottom_offset * 2);
|
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "H264 Profile: %d size: %dx%d\n", sps.profile_idc, *width, *height);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// use hardcoded value
|
|
||||||
static void parse_sps_video_size(uint8_t *sps_buffer, int len, int *width, int *height)
|
|
||||||
{
|
|
||||||
*width = 1280;
|
|
||||||
*height = 720;
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "We have no idea about the video size without decoding the video or actually parse the SPS, using hardcoded %dx%d\n", *width, *height);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void init_video_track(MP4FileHandle mp4, MP4TrackId *video, switch_frame_t *frame)
|
|
||||||
{
|
|
||||||
int width = 0;
|
|
||||||
int height = 0;
|
|
||||||
uint8_t *sps_buffer = frame->data;
|
|
||||||
uint32_t sps_bytes = frame->datalen;
|
|
||||||
|
|
||||||
sps_buffer++;
|
|
||||||
|
|
||||||
if (frame->img) {
|
|
||||||
width = frame->img->d_w;
|
|
||||||
height = frame->img->d_h;
|
|
||||||
} else {
|
|
||||||
parse_sps_video_size(sps_buffer, sps_bytes, &width, &height);
|
|
||||||
}
|
|
||||||
|
|
||||||
MP4SetTimeScale(mp4, TIMESCALE);
|
|
||||||
|
|
||||||
*video = MP4AddH264VideoTrack(mp4, TIMESCALE, MP4_INVALID_DURATION, width, height, *(sps_buffer), *(sps_buffer+1), *(sps_buffer+2), SampleLenFieldSize - 1);
|
|
||||||
|
|
||||||
if (*video == MP4_INVALID_TRACK_ID) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MP4AddH264SequenceParameterSet(mp4, *video, --sps_buffer, sps_bytes);
|
|
||||||
|
|
||||||
/*
|
|
||||||
MP4SetVideoProfileLevel sets the minumum profile/level of MPEG-4 video support necessary to render the contents of the file.
|
|
||||||
|
|
||||||
ISO/IEC 14496-1:2001 MPEG-4 Systems defines the following values:
|
|
||||||
0x00 Reserved
|
|
||||||
0x01 Simple Profile @ Level 3
|
|
||||||
0x02 Simple Profile @ Level 2
|
|
||||||
0x03 Simple Profile @ Level 1
|
|
||||||
0x04 Simple Scalable Profile @ Level 2
|
|
||||||
0x05 Simple Scalable Profile @ Level 1
|
|
||||||
0x06 Core Profile @ Level 2
|
|
||||||
0x07 Core Profile @ Level 1
|
|
||||||
0x08 Main Profile @ Level 4
|
|
||||||
0x09 Main Profile @ Level 3
|
|
||||||
0x0A Main Profile @ Level 2
|
|
||||||
0x0B N-Bit Profile @ Level 2
|
|
||||||
0x0C Hybrid Profile @ Level 2
|
|
||||||
0x0D Hybrid Profile @ Level 1
|
|
||||||
0x0E Basic Animated Texture @ Level 2
|
|
||||||
0x0F Basic Animated Texture @ Level 1
|
|
||||||
0x10 Scalable Texture @ Level 3
|
|
||||||
0x11 Scalable Texture @ Level 2
|
|
||||||
0x12 Scalable Texture @ Level 1
|
|
||||||
0x13 Simple Face Animation @ Level 2
|
|
||||||
0x14 Simple Face Animation @ Level 1
|
|
||||||
0x15-0x7F Reserved
|
|
||||||
0x80-0xFD User private
|
|
||||||
0xFE No audio profile specified
|
|
||||||
0xFF No audio required
|
|
||||||
*/
|
|
||||||
MP4SetVideoProfileLevel(mp4, 0x7F);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline char *get_audio_codec_name(uint8_t audio_type)
|
|
||||||
{
|
|
||||||
switch (audio_type) {
|
|
||||||
case MP4_MP3_AUDIO_TYPE:
|
|
||||||
return "MP3";
|
|
||||||
case MP4_ULAW_AUDIO_TYPE:
|
|
||||||
return "PCMU";
|
|
||||||
case MP4_PCM16_LITTLE_ENDIAN_AUDIO_TYPE:
|
|
||||||
return "L16";
|
|
||||||
case MP4_MPEG4_AUDIO_TYPE:
|
|
||||||
return "AAC";
|
|
||||||
default:
|
|
||||||
return "ERROR";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int get_aac_sample_rate_index(unsigned int sampleRate)
|
|
||||||
{
|
|
||||||
if (92017 <= sampleRate) return 0;
|
|
||||||
if (75132 <= sampleRate) return 1;
|
|
||||||
if (55426 <= sampleRate) return 2;
|
|
||||||
if (46009 <= sampleRate) return 3;
|
|
||||||
if (37566 <= sampleRate) return 4;
|
|
||||||
if (27713 <= sampleRate) return 5;
|
|
||||||
if (23004 <= sampleRate) return 6;
|
|
||||||
if (18783 <= sampleRate) return 7;
|
|
||||||
if (13856 <= sampleRate) return 8;
|
|
||||||
if (11502 <= sampleRate) return 9;
|
|
||||||
if (9391 <= sampleRate) return 10;
|
|
||||||
|
|
||||||
return 11;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct mp4_file_context {
|
|
||||||
switch_file_handle_t *handle;
|
|
||||||
switch_memory_pool_t *pool;
|
|
||||||
MP4FileHandle fd;
|
|
||||||
MP4TrackId audio;
|
|
||||||
MP4TrackId video;
|
|
||||||
uint32_t audio_frame_size;
|
|
||||||
switch_codec_t audio_codec;
|
|
||||||
switch_codec_t video_codec;
|
|
||||||
switch_mutex_t *mutex;
|
|
||||||
switch_buffer_t *buf;
|
|
||||||
uint32_t last_chunk_size;
|
|
||||||
int sps_set;
|
|
||||||
int pps_set;
|
|
||||||
switch_timer_t timer;
|
|
||||||
uint64_t last_pts;
|
|
||||||
int offset;
|
|
||||||
int audio_start;
|
|
||||||
uint8_t audio_type; // MP4 Audio Type
|
|
||||||
MP4Duration audio_duration;
|
|
||||||
switch_thread_t *video_thread;
|
|
||||||
switch_queue_t *video_queue;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct mp4_file_context mp4_file_context_t;
|
|
||||||
|
|
||||||
static switch_status_t do_write_video(switch_file_handle_t *handle, switch_frame_t *frame);
|
|
||||||
|
|
||||||
static void *SWITCH_THREAD_FUNC video_write_thread_run(switch_thread_t *thread, void *obj)
|
|
||||||
{
|
|
||||||
mp4_file_context_t *context = (mp4_file_context_t *)obj;
|
|
||||||
void *pop = NULL;
|
|
||||||
switch_image_t *last_img = NULL;
|
|
||||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
|
||||||
switch_status_t encode_status = SWITCH_STATUS_SUCCESS;
|
|
||||||
uint8_t data[SWITCH_DEFAULT_VIDEO_SIZE];
|
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "video_write_thread start\n");
|
|
||||||
|
|
||||||
while (switch_queue_pop(context->video_queue, &pop) == SWITCH_STATUS_SUCCESS) {
|
|
||||||
switch_frame_t frame = { 0 };
|
|
||||||
|
|
||||||
if (!pop) break;
|
|
||||||
|
|
||||||
if (!last_img) { // first img
|
|
||||||
last_img = (switch_image_t *)pop;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
frame.data = data;
|
|
||||||
frame.img = last_img;
|
|
||||||
// switch_set_flag(&frame, SFF_DYNAMIC);
|
|
||||||
|
|
||||||
do {
|
|
||||||
frame.datalen = SWITCH_DEFAULT_VIDEO_SIZE;
|
|
||||||
encode_status = switch_core_codec_encode_video(&context->video_codec, &frame);
|
|
||||||
|
|
||||||
if (encode_status == SWITCH_STATUS_SUCCESS || encode_status == SWITCH_STATUS_MORE_DATA) {
|
|
||||||
switch_assert((encode_status == SWITCH_STATUS_SUCCESS && frame.m) || !frame.m);
|
|
||||||
|
|
||||||
if (frame.datalen == 0) break;
|
|
||||||
|
|
||||||
status = do_write_video(context->handle, &frame);
|
|
||||||
}
|
|
||||||
} while(status == SWITCH_STATUS_SUCCESS && encode_status == SWITCH_STATUS_MORE_DATA);
|
|
||||||
|
|
||||||
switch_img_free(&last_img);
|
|
||||||
last_img = (switch_image_t *)pop;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_img_free(&last_img);
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "video_write_thread done\n");
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void launch_video_write_thread(mp4_file_context_t *context, switch_memory_pool_t *pool)
|
|
||||||
{
|
|
||||||
//switch_thread_t *thread;
|
|
||||||
switch_threadattr_t *thd_attr = NULL;
|
|
||||||
|
|
||||||
switch_threadattr_create(&thd_attr, pool);
|
|
||||||
switch_threadattr_detach_set(thd_attr, 1);
|
|
||||||
switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
|
|
||||||
// switch_threadattr_priority_set(thd_attr, SWITCH_PRI_REALTIME);
|
|
||||||
switch_thread_create(&context->video_thread, thd_attr, video_write_thread_run, context, pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int flush_video_queue(switch_queue_t *q, int min)
|
|
||||||
{
|
|
||||||
void *pop;
|
|
||||||
|
|
||||||
if (switch_queue_size(q) > min) {
|
|
||||||
while (switch_queue_trypop(q, &pop) == SWITCH_STATUS_SUCCESS) {
|
|
||||||
switch_image_t *img = (switch_image_t *) pop;
|
|
||||||
switch_img_free(&img);
|
|
||||||
if (min && switch_queue_size(q) <= min) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return switch_queue_size(q);
|
|
||||||
}
|
|
||||||
|
|
||||||
static switch_status_t mp4_file_open(switch_file_handle_t *handle, const char *path)
|
|
||||||
{
|
|
||||||
mp4_file_context_t *context;
|
|
||||||
char *ext;
|
|
||||||
unsigned int flags = 0;
|
|
||||||
const char *tmp = NULL;
|
|
||||||
|
|
||||||
if ((ext = strrchr(path, '.')) == 0) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid Format\n");
|
|
||||||
return SWITCH_STATUS_GENERR;
|
|
||||||
}
|
|
||||||
ext++;
|
|
||||||
|
|
||||||
if ((context = switch_core_alloc(handle->memory_pool, sizeof(mp4_file_context_t))) == 0) {
|
|
||||||
return SWITCH_STATUS_MEMERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(context, 0, sizeof(mp4_file_context_t));
|
|
||||||
|
|
||||||
context->handle = handle;
|
|
||||||
context->offset = 0;
|
|
||||||
|
|
||||||
if (handle->params && (tmp = switch_event_get_header(handle->params, "mp4v2_video_offset"))) {
|
|
||||||
context->offset = atoi(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
context->audio_type = MP4_ULAW_AUDIO_TYPE; // default
|
|
||||||
|
|
||||||
if (handle->params && (tmp = switch_event_get_header(handle->params, "mp4v2_audio_codec"))) {
|
|
||||||
if (!strcasecmp(tmp, "PCMU")) {
|
|
||||||
context->audio_type = MP4_ULAW_AUDIO_TYPE;
|
|
||||||
} else if (!strcasecmp(tmp, "MP3")) {
|
|
||||||
context->audio_type = MP4_MP3_AUDIO_TYPE;
|
|
||||||
} else if (!strcasecmp(tmp, "AAC")) {
|
|
||||||
context->audio_type = MP4_MPEG4_AUDIO_TYPE;
|
|
||||||
} else if (!strcasecmp(tmp, "L16")) {
|
|
||||||
context->audio_type = MP4_PCM16_LITTLE_ENDIAN_AUDIO_TYPE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_mutex_init(&context->mutex, SWITCH_MUTEX_NESTED, handle->memory_pool);
|
|
||||||
|
|
||||||
if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) {
|
|
||||||
flags |= SWITCH_FOPEN_WRITE | SWITCH_FOPEN_CREATE;
|
|
||||||
if (switch_test_flag(handle, SWITCH_FILE_WRITE_APPEND) || switch_test_flag(handle, SWITCH_FILE_WRITE_OVER)) {
|
|
||||||
flags |= SWITCH_FOPEN_READ;
|
|
||||||
} else {
|
|
||||||
flags |= SWITCH_FOPEN_TRUNCATE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (switch_test_flag(handle, SWITCH_FILE_FLAG_READ)) {
|
|
||||||
flags |= SWITCH_FOPEN_READ;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handle->mm.samplerate) {
|
|
||||||
handle->samplerate = handle->mm.samplerate;
|
|
||||||
} else {
|
|
||||||
handle->mm.samplerate = handle->samplerate;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!handle->mm.ab) {
|
|
||||||
handle->mm.ab = 128;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!handle->mm.vb) {
|
|
||||||
handle->mm.vb = switch_calc_bitrate(handle->mm.vw, handle->mm.vh, 1, handle->mm.fps);
|
|
||||||
}
|
|
||||||
|
|
||||||
// MP4_CREATE_64BIT_DATA if file > 4G
|
|
||||||
|
|
||||||
if ((context->fd = MP4CreateEx(path, 0, 1, 1, NULL, 0, NULL, 0)) == MP4_INVALID_FILE_HANDLE) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Error opening file %s\n", path);
|
|
||||||
return SWITCH_STATUS_GENERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "sample rate: %d, channels: %d\n", handle->samplerate, handle->channels);
|
|
||||||
|
|
||||||
if (context->audio_type == MP4_ULAW_AUDIO_TYPE) {
|
|
||||||
context->audio = MP4AddULawAudioTrack(context->fd, handle->samplerate);
|
|
||||||
MP4SetTrackIntegerProperty(context->fd, context->audio, "mdia.minf.stbl.stsd.ulaw.channels", handle->channels);
|
|
||||||
MP4SetTrackIntegerProperty(context->fd, context->audio, "mdia.minf.stbl.stsd.ulaw.sampleSize", 8);
|
|
||||||
} else if (context->audio_type == MP4_MP3_AUDIO_TYPE) {
|
|
||||||
// handle->samplerate = 44100;
|
|
||||||
context->audio = MP4AddAudioTrack(context->fd, handle->samplerate, handle->samplerate, MP4_MP3_AUDIO_TYPE);
|
|
||||||
MP4SetTrackName(context->fd, context->audio, ".mp3");
|
|
||||||
MP4SetTrackIntegerProperty(context->fd, context->audio, "mdia.minf.stbl.stsd.mp4a.channels", handle->channels);
|
|
||||||
// MP4SetTrackIntegerProperty(context->fd, context->audio, "mdia.minf.stbl.stsd..mp3.channels", handle->channels);
|
|
||||||
} else if (context->audio_type == MP4_PCM16_LITTLE_ENDIAN_AUDIO_TYPE) {
|
|
||||||
context->audio = MP4AddAudioTrack(context->fd, handle->samplerate, handle->samplerate, MP4_PCM16_LITTLE_ENDIAN_AUDIO_TYPE);
|
|
||||||
MP4SetTrackName(context->fd, context->audio, "lpcm");
|
|
||||||
MP4SetTrackIntegerProperty(context->fd, context->audio, "mdia.minf.stbl.stsd.mp4a.channels", handle->channels);
|
|
||||||
MP4SetTrackIntegerProperty(context->fd, context->audio, "mdia.minf.stbl.stsd.lpcm.channels", handle->channels);
|
|
||||||
MP4SetTrackIntegerProperty(context->fd, context->audio, "mdia.minf.stbl.stsd.lpcm.sampleSize", 16);
|
|
||||||
} else if (context->audio_type == MP4_MPEG4_AUDIO_TYPE) {
|
|
||||||
/* AAC object types */
|
|
||||||
#define AAC_MAIN 1
|
|
||||||
#define AAC_LOW 2
|
|
||||||
#define AAC_SSR 3
|
|
||||||
#define AAC_LTP 4
|
|
||||||
|
|
||||||
uint16_t info = 0;
|
|
||||||
|
|
||||||
info |= AAC_LOW << 11; // aacObjectType (5bit)
|
|
||||||
info |= get_aac_sample_rate_index(handle->samplerate) << 7; //(4bit)
|
|
||||||
info |= handle->channels << 3; //(4bit)
|
|
||||||
info = htons(info);
|
|
||||||
|
|
||||||
context->audio = MP4AddAudioTrack(context->fd, handle->samplerate, 1024, MP4_MPEG4_AUDIO_TYPE);
|
|
||||||
MP4SetTrackESConfiguration(context->fd, context->audio, (uint8_t *)&info, sizeof(info));
|
|
||||||
MP4SetTrackIntegerProperty(context->fd, context->audio, "mdia.minf.stbl.stsd.mp4a.channels", handle->channels);
|
|
||||||
}
|
|
||||||
|
|
||||||
handle->format = 0;
|
|
||||||
handle->sections = 0;
|
|
||||||
handle->seekable = 0;
|
|
||||||
handle->speed = 0;
|
|
||||||
handle->pos = 0;
|
|
||||||
handle->private_info = context;
|
|
||||||
context->pool = handle->memory_pool;
|
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Opening File [%s] %dhz %s\n",
|
|
||||||
path, handle->samplerate, switch_test_flag(handle, SWITCH_FILE_FLAG_VIDEO) ? " with VIDEO" : "");
|
|
||||||
|
|
||||||
if (switch_core_codec_init(&context->audio_codec,
|
|
||||||
get_audio_codec_name(context->audio_type),
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
handle->samplerate,
|
|
||||||
PTIME,//ms
|
|
||||||
handle->channels, SWITCH_CODEC_FLAG_ENCODE,
|
|
||||||
NULL, handle->memory_pool) == SWITCH_STATUS_SUCCESS) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Audio Codec Activation Success\n");
|
|
||||||
} else {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Audio Codec Activation Fail\n");
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context->audio_type == MP4_MP3_AUDIO_TYPE) { // fetch frame size
|
|
||||||
uint32_t size;
|
|
||||||
uint32_t flag = 0xFFFFFFFF;
|
|
||||||
|
|
||||||
switch_core_codec_encode(&context->audio_codec, NULL, &flag, 0, 0,
|
|
||||||
(void *)&context->audio_frame_size, &size, NULL, &flag);
|
|
||||||
} else if (context->audio_type == MP4_MPEG4_AUDIO_TYPE) {
|
|
||||||
context->audio_frame_size = 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (switch_test_flag(handle, SWITCH_FILE_FLAG_VIDEO)) {
|
|
||||||
switch_codec_settings_t codec_settings = {{ 0 }};
|
|
||||||
codec_settings.video.bandwidth = handle->mm.vb;
|
|
||||||
codec_settings.video.fps = handle->mm.fps;
|
|
||||||
|
|
||||||
if (switch_core_codec_init(&context->video_codec,
|
|
||||||
"H264",
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
90000,
|
|
||||||
0,//ms
|
|
||||||
1, SWITCH_CODEC_FLAG_ENCODE,
|
|
||||||
&codec_settings, handle->memory_pool) == SWITCH_STATUS_SUCCESS) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Video Codec H264 Activation Success\n");
|
|
||||||
} else {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Video Codec H264 Activation Fail\n");
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_queue_create(&context->video_queue, 60, handle->memory_pool);
|
|
||||||
launch_video_write_thread(context, handle->memory_pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) {
|
|
||||||
MP4SetAudioProfileLevel(context->fd, 0x7F);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_buffer_create_dynamic(&context->buf, 512, 512, 1024000);
|
|
||||||
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
|
||||||
|
|
||||||
end:
|
|
||||||
if (context->fd) {
|
|
||||||
MP4Close(context->fd, 0);
|
|
||||||
context->fd = NULL;
|
|
||||||
}
|
|
||||||
return SWITCH_STATUS_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static switch_status_t mp4_file_truncate(switch_file_handle_t *handle, int64_t offset)
|
|
||||||
{
|
|
||||||
mp4_file_context_t *context = handle->private_info;
|
|
||||||
switch_status_t status;
|
|
||||||
|
|
||||||
if ((status = switch_file_trunc(context->fd, offset)) == SWITCH_STATUS_SUCCESS) {
|
|
||||||
handle->pos = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static switch_status_t mp4_file_close(switch_file_handle_t *handle)
|
|
||||||
{
|
|
||||||
mp4_file_context_t *context = handle->private_info;
|
|
||||||
switch_status_t status;
|
|
||||||
|
|
||||||
if (context->fd) {
|
|
||||||
MP4Close(context->fd, MP4_CLOSE_DO_NOT_COMPUTE_BITRATE);
|
|
||||||
context->fd = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (switch_core_codec_ready(&context->audio_codec)) switch_core_codec_destroy(&context->audio_codec);
|
|
||||||
if (switch_core_codec_ready(&context->video_codec)) switch_core_codec_destroy(&context->video_codec);
|
|
||||||
|
|
||||||
if (context->timer.interval) {
|
|
||||||
switch_core_timer_destroy(&context->timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context->video_queue) {
|
|
||||||
switch_queue_term(context->video_queue);
|
|
||||||
flush_video_queue(context->video_queue, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context->video_thread) {
|
|
||||||
switch_thread_join(&status, context->video_thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_buffer_destroy(&context->buf);
|
|
||||||
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
static switch_status_t mp4_file_seek(switch_file_handle_t *handle, unsigned int *cur_sample, int64_t samples, int whence)
|
|
||||||
{
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "seek not implemented\n");
|
|
||||||
return SWITCH_STATUS_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static switch_status_t mp4_file_read(switch_file_handle_t *handle, void *data, size_t *len)
|
|
||||||
{
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "read not implemented\n");
|
|
||||||
return SWITCH_STATUS_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static switch_status_t mp4_file_write(switch_file_handle_t *handle, void *data, size_t *len)
|
|
||||||
{
|
|
||||||
uint32_t datalen = *len * 2 * handle->channels;
|
|
||||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
|
||||||
uint8_t buf[SWITCH_RECOMMENDED_BUFFER_SIZE];
|
|
||||||
uint32_t encoded_rate;
|
|
||||||
mp4_file_context_t *context = handle->private_info;
|
|
||||||
uint32_t size = 0;
|
|
||||||
uint32_t flag = 0;
|
|
||||||
|
|
||||||
if (context->audio_type == MP4_PCM16_LITTLE_ENDIAN_AUDIO_TYPE) {
|
|
||||||
size = datalen;
|
|
||||||
memcpy(buf, data, datalen);
|
|
||||||
} else {
|
|
||||||
switch_core_codec_encode(&context->audio_codec, NULL,
|
|
||||||
data, datalen,
|
|
||||||
handle->samplerate,
|
|
||||||
buf, &size, &encoded_rate, &flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_mutex_lock(context->mutex);
|
|
||||||
|
|
||||||
if (!context->timer.interval) {
|
|
||||||
switch_core_timer_init(&context->timer, "soft", 1, 1, context->pool);
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "init timer\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size > 0) {
|
|
||||||
MP4WriteSample(context->fd, context->audio, buf, size, context->audio_frame_size ? context->audio_frame_size : *len, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
context->audio_duration += *len;
|
|
||||||
|
|
||||||
switch_mutex_unlock(context->mutex);
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
static switch_status_t mp4_file_read_video(switch_file_handle_t *handle, switch_frame_t *frame, switch_video_read_flag_t flags)
|
|
||||||
{
|
|
||||||
return SWITCH_STATUS_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static switch_status_t do_write_video(switch_file_handle_t *handle, switch_frame_t *frame)
|
|
||||||
{
|
|
||||||
uint32_t datalen = frame->datalen;
|
|
||||||
switch_status_t status = SWITCH_STATUS_SUCCESS;
|
|
||||||
int is_iframe = 0;
|
|
||||||
uint32_t size;
|
|
||||||
uint8_t *hdr = NULL;
|
|
||||||
uint8_t fragment_type;
|
|
||||||
uint8_t nal_type;
|
|
||||||
uint8_t start_bit;
|
|
||||||
uint8_t end_bit;
|
|
||||||
mp4_file_context_t *context = handle->private_info;
|
|
||||||
|
|
||||||
hdr = (uint8_t *)frame->data;
|
|
||||||
fragment_type = hdr[0] & 0x1f;
|
|
||||||
nal_type = hdr[1] & 0x1f;
|
|
||||||
start_bit = hdr[1] & 0x80;
|
|
||||||
end_bit = hdr[1] & 0x40;
|
|
||||||
|
|
||||||
is_iframe = fragment_type == 5 || (fragment_type == 28 && nal_type == 5);
|
|
||||||
|
|
||||||
// switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%02x %02x %02x | len:%d m:%d st:%d i:%d\n", hdr[0], hdr[1], hdr[2], datalen, frame->m, start_bit, is_iframe);
|
|
||||||
|
|
||||||
if (fragment_type == 7 && !context->sps_set) { //sps
|
|
||||||
context->sps_set = 1;
|
|
||||||
|
|
||||||
init_video_track(context->fd, &context->video, frame);
|
|
||||||
if (context->video == MP4_INVALID_TRACK_ID) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error add video track!\n");
|
|
||||||
return SWITCH_STATUS_FALSE;
|
|
||||||
}
|
|
||||||
} else if (fragment_type == 8 && context->sps_set && !context->pps_set) { //pps
|
|
||||||
MP4AddH264PictureParameterSet(context->fd, context->video, hdr, datalen);
|
|
||||||
context->pps_set = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fragment_type == 28) {
|
|
||||||
if (start_bit && end_bit) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "WTF?\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start_bit) {
|
|
||||||
nal_type |= (hdr[0] & 0x60);
|
|
||||||
|
|
||||||
size = htonl(datalen);
|
|
||||||
switch_buffer_write(context->buf, &size, 4);
|
|
||||||
switch_buffer_write(context->buf, &nal_type, 1);
|
|
||||||
switch_buffer_write(context->buf, hdr + 2, datalen - 2);
|
|
||||||
context->last_chunk_size = datalen - 1;
|
|
||||||
} else if (end_bit) {
|
|
||||||
uint32_t used;
|
|
||||||
const void *data;
|
|
||||||
uint32_t *chunk_size = NULL;
|
|
||||||
|
|
||||||
switch_buffer_write(context->buf, hdr + 2, datalen - 2);
|
|
||||||
context->last_chunk_size += datalen - 2;
|
|
||||||
used = switch_buffer_inuse(context->buf);
|
|
||||||
switch_buffer_peek_zerocopy(context->buf, &data);
|
|
||||||
chunk_size = (uint32_t *)((uint8_t *)data + used - context->last_chunk_size - 4);
|
|
||||||
*chunk_size = htonl(context->last_chunk_size);
|
|
||||||
} else {
|
|
||||||
switch_buffer_write(context->buf, hdr + 2, datalen - 2);
|
|
||||||
context->last_chunk_size += datalen - 2;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
size = htonl(datalen);
|
|
||||||
switch_buffer_write(context->buf, &size, 4);
|
|
||||||
switch_buffer_write(context->buf, hdr, datalen);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!frame->m) {
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_mutex_lock(context->mutex);
|
|
||||||
|
|
||||||
if (context->sps_set && context->pps_set) {
|
|
||||||
uint32_t used = switch_buffer_inuse(context->buf);
|
|
||||||
const void *data;
|
|
||||||
int duration = 0;
|
|
||||||
int ts = 0;
|
|
||||||
|
|
||||||
if (frame->img && frame->img->user_priv) {
|
|
||||||
ts = *(int *)frame->img->user_priv;
|
|
||||||
} else {
|
|
||||||
switch_core_timer_sync(&context->timer);
|
|
||||||
ts = context->timer.samplecount;
|
|
||||||
}
|
|
||||||
|
|
||||||
duration = ts - context->last_pts;
|
|
||||||
|
|
||||||
if (duration <= 0) duration = 1;
|
|
||||||
|
|
||||||
// switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "samplecount: %u, duration: %u\n", context->timer.samplecount, duration);
|
|
||||||
switch_buffer_peek_zerocopy(context->buf, &data);
|
|
||||||
|
|
||||||
context->last_pts = ts;
|
|
||||||
|
|
||||||
MP4WriteSample(context->fd, context->video, data, used, duration, 0, is_iframe);
|
|
||||||
switch_buffer_zero(context->buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_mutex_unlock(context->mutex);
|
|
||||||
|
|
||||||
{
|
|
||||||
int delta = context->timer.samplecount * (handle->samplerate / 1000) - context->audio_duration;
|
|
||||||
|
|
||||||
if (delta > (int)handle->samplerate) {
|
|
||||||
uint8_t data[SWITCH_RECOMMENDED_BUFFER_SIZE] = { 0 };
|
|
||||||
size_t samples = handle->samplerate / 1000 * PTIME;
|
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "missed audio %d samples at %d\n", delta, (int)context->audio_duration / (handle->samplerate / 1000));
|
|
||||||
|
|
||||||
while ((delta -= samples) > 0) {
|
|
||||||
mp4_file_write(handle, data, &samples);
|
|
||||||
samples = handle->samplerate / 1000 * PTIME;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
static switch_status_t mp4_file_write_video(switch_file_handle_t *handle, switch_frame_t *frame)
|
|
||||||
{
|
|
||||||
mp4_file_context_t *context = handle->private_info;
|
|
||||||
|
|
||||||
if (!frame->img) {
|
|
||||||
return do_write_video(handle, frame);
|
|
||||||
} else {
|
|
||||||
switch_image_t *img = NULL;
|
|
||||||
|
|
||||||
if (!context->timer.interval) {
|
|
||||||
switch_mutex_lock(context->mutex);
|
|
||||||
switch_core_timer_init(&context->timer, "soft", 1, 1, context->pool);
|
|
||||||
switch_mutex_unlock(context->mutex);
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "init timer\n");
|
|
||||||
} else {
|
|
||||||
switch_mutex_lock(context->mutex);
|
|
||||||
switch_core_timer_sync(&context->timer);
|
|
||||||
switch_mutex_unlock(context->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_img_copy(frame->img, &img);
|
|
||||||
switch_assert(img);
|
|
||||||
img->user_priv = malloc(sizeof(int));
|
|
||||||
*(int *)img->user_priv = context->timer.samplecount;
|
|
||||||
|
|
||||||
if (switch_queue_trypush(context->video_queue, img) != SWITCH_STATUS_SUCCESS) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "video queue full, discard one frame\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
static switch_status_t mp4_file_set_string(switch_file_handle_t *handle, switch_audio_col_t col, const char *string)
|
|
||||||
{
|
|
||||||
return SWITCH_STATUS_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static switch_status_t mp4_file_get_string(switch_file_handle_t *handle, switch_audio_col_t col, const char **string)
|
|
||||||
{
|
|
||||||
return SWITCH_STATUS_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *supported_formats[3] = { 0 };
|
|
||||||
|
|
||||||
SWITCH_MODULE_LOAD_FUNCTION(mod_mp4v2_load)
|
|
||||||
{
|
|
||||||
switch_file_interface_t *file_interface;
|
|
||||||
|
|
||||||
supported_formats[0] = "mp4v2";
|
|
||||||
supported_formats[1] = "mp4";
|
|
||||||
|
|
||||||
/* connect my internal structure to the blank pointer passed to me */
|
|
||||||
*module_interface = switch_loadable_module_create_module_interface(pool, modname);
|
|
||||||
|
|
||||||
file_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_FILE_INTERFACE);
|
|
||||||
file_interface->interface_name = modname;
|
|
||||||
file_interface->extens = supported_formats;
|
|
||||||
file_interface->file_open = mp4_file_open;
|
|
||||||
file_interface->file_close = mp4_file_close;
|
|
||||||
file_interface->file_truncate = mp4_file_truncate;
|
|
||||||
file_interface->file_read = mp4_file_read;
|
|
||||||
file_interface->file_write = mp4_file_write;
|
|
||||||
file_interface->file_read_video = mp4_file_read_video;
|
|
||||||
file_interface->file_write_video = mp4_file_write_video;
|
|
||||||
file_interface->file_seek = mp4_file_seek;
|
|
||||||
file_interface->file_set_string = mp4_file_set_string;
|
|
||||||
file_interface->file_get_string = mp4_file_get_string;
|
|
||||||
|
|
||||||
/* indicate that the module should continue to be loaded */
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* For Emacs:
|
|
||||||
* Local Variables:
|
|
||||||
* mode:c
|
|
||||||
* indent-tabs-mode:t
|
|
||||||
* tab-width:4
|
|
||||||
* c-basic-offset:4
|
|
||||||
* End:
|
|
||||||
* For VIM:
|
|
||||||
* vim:set softtabstop=4 shiftwidth=4 tabstop=4:
|
|
||||||
*/
|
|
|
@ -1,8 +0,0 @@
|
||||||
include $(top_srcdir)/build/modmake.rulesam
|
|
||||||
MODNAME=mod_mp4v
|
|
||||||
|
|
||||||
mod_LTLIBRARIES = mod_mp4v.la
|
|
||||||
mod_mp4v_la_SOURCES = mod_mp4v.c
|
|
||||||
mod_mp4v_la_CFLAGS = $(AM_CFLAGS)
|
|
||||||
mod_mp4v_la_LIBADD = $(switch_builddir)/libfreeswitch.la
|
|
||||||
mod_mp4v_la_LDFLAGS = -avoid-version -module -no-undefined -shared
|
|
|
@ -1,102 +0,0 @@
|
||||||
/*
|
|
||||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
|
||||||
* Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
|
|
||||||
*
|
|
||||||
* Version: MPL 1.1
|
|
||||||
*
|
|
||||||
* The contents of this file are subject to the Mozilla Public License Version
|
|
||||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
* http://www.mozilla.org/MPL/
|
|
||||||
*
|
|
||||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
||||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
||||||
* for the specific language governing rights and limitations under the
|
|
||||||
* License.
|
|
||||||
*
|
|
||||||
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
|
||||||
*
|
|
||||||
* The Initial Developer of the Original Code is
|
|
||||||
* Anthony Minessale II <anthm@freeswitch.org>
|
|
||||||
* Portions created by the Initial Developer are Copyright (C)
|
|
||||||
* the Initial Developer. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Contributor(s):
|
|
||||||
*
|
|
||||||
* Anthony Minessale II <anthm@freeswitch.org>
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* mod_mp4v.c -- MP4V Video Codec
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
SWITCH_MODULE_LOAD_FUNCTION(mod_mp4v_load);
|
|
||||||
SWITCH_MODULE_DEFINITION(mod_mp4v, mod_mp4v_load, NULL, NULL);
|
|
||||||
|
|
||||||
static switch_status_t switch_mp4v_init(switch_codec_t *codec, switch_codec_flag_t flags, const switch_codec_settings_t *codec_settings)
|
|
||||||
{
|
|
||||||
int encoding, decoding;
|
|
||||||
|
|
||||||
encoding = (flags & SWITCH_CODEC_FLAG_ENCODE);
|
|
||||||
decoding = (flags & SWITCH_CODEC_FLAG_DECODE);
|
|
||||||
|
|
||||||
if (!(encoding || decoding)) {
|
|
||||||
return SWITCH_STATUS_FALSE;
|
|
||||||
} else {
|
|
||||||
if (codec->fmtp_in) {
|
|
||||||
codec->fmtp_out = switch_core_strdup(codec->memory_pool, codec->fmtp_in);
|
|
||||||
}
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static switch_status_t switch_mp4v_encode(switch_codec_t *codec,
|
|
||||||
switch_codec_t *other_codec,
|
|
||||||
void *decoded_data,
|
|
||||||
uint32_t decoded_data_len,
|
|
||||||
uint32_t decoded_rate, void *encoded_data, uint32_t *encoded_data_len, uint32_t *encoded_rate,
|
|
||||||
unsigned int *flag)
|
|
||||||
{
|
|
||||||
return SWITCH_STATUS_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static switch_status_t switch_mp4v_decode(switch_codec_t *codec,
|
|
||||||
switch_codec_t *other_codec,
|
|
||||||
void *encoded_data,
|
|
||||||
uint32_t encoded_data_len,
|
|
||||||
uint32_t encoded_rate, void *decoded_data, uint32_t *decoded_data_len, uint32_t *decoded_rate,
|
|
||||||
unsigned int *flag)
|
|
||||||
{
|
|
||||||
return SWITCH_STATUS_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static switch_status_t switch_mp4v_destroy(switch_codec_t *codec)
|
|
||||||
{
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
SWITCH_MODULE_LOAD_FUNCTION(mod_mp4v_load)
|
|
||||||
{
|
|
||||||
switch_codec_interface_t *codec_interface;
|
|
||||||
/* connect my internal structure to the blank pointer passed to me */
|
|
||||||
*module_interface = switch_loadable_module_create_module_interface(pool, modname);
|
|
||||||
SWITCH_ADD_CODEC(codec_interface, "MP4V Video (passthru)");
|
|
||||||
switch_core_codec_add_implementation(pool, codec_interface,
|
|
||||||
SWITCH_CODEC_TYPE_VIDEO, 99, "MP4V-ES", NULL, 90000, 90000, 0,
|
|
||||||
0, 0, 0, 0, 1, 1, switch_mp4v_init, switch_mp4v_encode, switch_mp4v_decode, switch_mp4v_destroy);
|
|
||||||
/* indicate that the module should continue to be loaded */
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* For Emacs:
|
|
||||||
* Local Variables:
|
|
||||||
* mode:c
|
|
||||||
* indent-tabs-mode:t
|
|
||||||
* tab-width:4
|
|
||||||
* c-basic-offset:4
|
|
||||||
* End:
|
|
||||||
* For VIM:
|
|
||||||
* vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
|
|
||||||
*/
|
|
Loading…
Reference in New Issue