diff --git a/src/mod/applications/mod_ffmpeg/mod_ffmpeg.c b/src/mod/applications/mod_ffmpeg/mod_ffmpeg.c new file mode 100644 index 0000000000..c91e4e36e3 --- /dev/null +++ b/src/mod/applications/mod_ffmpeg/mod_ffmpeg.c @@ -0,0 +1,2155 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2013, Anthony Minessale II + * + * 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 + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Seven Du + * + * mod_ffmpeg -- FS Video File Format + * + */ + +/* compile: need to remove the -pedantic option from modmake.rules to compile */ + +#include +#include +#include +#include +#include + +/* use libx264, comment for use the ffmpeg/avcodec wrapper */ +#define H264_CODEC_USE_LIBX264 +#define SLICE_SIZE SWITCH_DEFAULT_VIDEO_SIZE + +#ifdef H264_CODEC_USE_LIBX264 +#include +#endif + +#define FPS 15 // frame rate +#define BUFFERSIZE 4096 * 2 + +SWITCH_MODULE_LOAD_FUNCTION(mod_ffmpeg_load); +SWITCH_MODULE_DEFINITION(mod_ffmpeg, mod_ffmpeg_load, NULL, NULL); + +/* ff_avc_find_startcode is not esposed in the ffmpeg lib but you can use it + Either include the avc.h which available in the ffmpeg source, or + just add the declaration like we does following to avoid include that whole avc.h + The function is implemented in avc.h, guess we'll get rid of this later if we can directly use libx264 + +#include +*/ + +const uint8_t *ff_avc_find_startcode(const uint8_t *p, const uint8_t *end); + +#if 0 // this is not fully tested and supported +struct record_helper { + switch_core_session_t *session; + switch_mutex_t *mutex; + AVFormatContext *avctx; + AVStream *video_st; + AVFrame *avframe; + int up; + + int seq; + switch_socket_t *sock; + switch_sockaddr_t *sock_addr; +}; + +struct ffmpeg_context { + switch_mutex_t *mutex; + switch_buffer_t *buf; + AVFormatContext *avctx; + AVStream *video_st; + AVStream *audio_st; + AVFrame *aframe; + AVCodec *acodec; + int file_opened; + int video_pts; +}; + +typedef struct ffmpeg_context ffmpeg_context; + +static AVStream *add_audio_stream(AVFormatContext *context, enum AVCodecID codec_id) +{ + AVCodecContext *cc; + AVStream *st; + AVCodec *codec; + + /* find the audio encoder */ + codec = avcodec_find_encoder(codec_id); + if (!codec) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "codec not found\n"); + return NULL; + } + + st = avformat_new_stream(context, codec); + + if (!st) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not alloc audio stream\n"); + return NULL; + } + + st->id = 1; + + cc = st->codec; + cc->codec = codec; + + /* put sample parameters */ + cc->sample_fmt = AV_SAMPLE_FMT_S16; + cc->bit_rate = 64000; + cc->sample_rate = 44100; + cc->channels = 1; + + // some formats want stream headers to be separate + if (context->oformat->flags & AVFMT_GLOBALHEADER) { + cc->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + + return st; +} + +static AVStream *add_video_stream(AVFormatContext *context, enum AVCodecID codec_id) +{ + AVCodecContext *cc; + AVStream *st; + AVCodec *codec; + + /* find the video encoder */ + codec = avcodec_find_encoder(codec_id); + if (!codec) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "codec not found\n"); + return NULL; + } + + st = avformat_new_stream(context, codec); + if (!st) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not alloc stream\n"); + return NULL; + } + + cc = st->codec; + + avcodec_get_context_defaults3(cc, codec); + + cc->codec_id = codec_id; + + /* Put sample parameters. */ + // c->bit_rate = 400000; + /* Resolution must be a multiple of two. */ + cc->width = 352; + cc->height = 288; + /* timebase: This is the fundamental unit of time (in seconds) in terms + * of which frame timestamps are represented. For fixed-fps content, + * timebase should be 1/framerate and timestamp increments should be + * identical to 1. */ + cc->time_base.den = FPS; + cc->time_base.num = 1; + cc->gop_size = 12; /* emit one intra frame every twelve frames at most */ + cc->pix_fmt = PIX_FMT_YUV420P; + cc->max_b_frames = 0; + + /* Some formats want stream headers to be separate. */ + if (context->oformat->flags & AVFMT_GLOBALHEADER) { + cc->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "flags %x %x\n", context->oformat->flags, cc->flags); + + return st; +} + +static switch_status_t ffmpeg_open(switch_file_handle_t *handle, const char *path) +{ + ffmpeg_context *context; + char *ext; + unsigned int flags = 0; + AVFormatContext *avctx = NULL; + AVOutputFormat *fmt; + AVStream *audio_st=NULL; + AVStream *video_st=NULL; + AVCodecContext *audio_codec; + AVFrame *aframe = 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(ffmpeg_context))) == 0) { + return SWITCH_STATUS_MEMERR; + } + + memset(context, 0, sizeof(ffmpeg_context)); + + 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; + } + +//write only + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "sample rate: %d\n", handle->samplerate); + + handle->samplerate = 8000; + + aframe = av_frame_alloc(); + + if (!aframe) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "memory error!\n"); + goto end; + } + + context->aframe = aframe; + + av_register_all(); + + //avformat_alloc_output_context2(&avctx, NULL, NULL, path); + avctx = NULL; + + if (!avctx) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not deduce output format from file extension: using mpeg4.\n"); + //avformat_alloc_output_context2(&avctx, NULL, "mpeg4", path); + } + + if (!avctx) { + goto end; + } + + context->avctx = avctx; + + fmt = avctx->oformat; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "audio_codec: 0x%x video_codec: 0x%x\n", fmt->audio_codec, fmt->video_codec); + + if (fmt->audio_codec != AV_CODEC_ID_NONE) { + audio_st = add_audio_stream(avctx, fmt->audio_codec); + } + + if (fmt->video_codec != AV_CODEC_ID_NONE) { + video_st = add_video_stream(avctx, fmt->video_codec); + } + + if (!video_st) { + if (!audio_st) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No audio & video stream!\n"); + goto end; + } + } + + context->video_st = video_st; + + audio_codec = audio_st->codec; + /* open it */ + if (avcodec_open2(audio_codec, NULL, NULL) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "could not open audio codec\n"); + goto end; + } + +#if 1 + av_dump_format(avctx, 0, path, 1); +#endif + + /* open the output file, if needed */ + if (!(fmt->flags & AVFMT_NOFILE)) { + if (avio_open(&avctx->pb, path, AVIO_FLAG_WRITE) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not open [%s]\n", path); + goto end; + } + context->file_opened = 1; + } + + /* Write the stream header, if any. */ + avformat_write_header(avctx, NULL); + + context->audio_st = audio_st; + + if (switch_test_flag(handle, SWITCH_FILE_WRITE_APPEND)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "unsupported append\n"); + } + + handle->samples = 0; + handle->samplerate = 8000; + handle->channels = 1; + handle->format = 0; + handle->sections = 0; + handle->seekable = 0; + handle->speed = 0; + handle->pos = 0; + handle->private_info = context; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Opening File [%s] %dhz\n", path, handle->samplerate); + + if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) { + + } + + switch_buffer_create_dynamic(&context->buf, 512, 512, 1024000); + + return SWITCH_STATUS_SUCCESS; + +end: + + if (avctx) { + int i; + for (i = 0; i < avctx->nb_streams; i++) { + avcodec_close(avctx->streams[i]->codec); + av_freep(&avctx->streams[i]->codec); + av_freep(&avctx->streams[i]); + } + + if (context->file_opened) { + /* Close the output file. */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "closing file\n"); + avio_close(avctx->pb); + } + + av_free(avctx); + + if (aframe) av_free(aframe); + } + + return SWITCH_STATUS_FALSE; +} + +static switch_status_t ffmpeg_truncate(switch_file_handle_t *handle, int64_t offset) +{ + // ffmpeg_context *context = handle->private_info; + switch_status_t status = SWITCH_STATUS_FALSE; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "unsupported truncate\n"); + return status; +} + +static switch_status_t ffmpeg_close(switch_file_handle_t *handle) +{ + ffmpeg_context *context = handle->private_info; + AVFormatContext *avctx = context->avctx; + int i; + + av_write_trailer(avctx); + + for (i = 0; i < avctx->nb_streams; i++) { + avcodec_close(avctx->streams[i]->codec); + av_freep(&avctx->streams[i]->codec); + av_freep(&avctx->streams[i]); + } + + if (context->file_opened) { + /* Close the output file. */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "closing file\n"); + avio_close(avctx->pb); + } + + av_free(avctx); + + if (context->aframe) av_free(context->aframe); + + switch_buffer_destroy(&context->buf); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t ffmpeg_seek(switch_file_handle_t *handle, unsigned int *cur_sample, int64_t samples, int whence) +{ + // switch_status_t status; + // + // ffmpeg_context *context = handle->private_info; + // + // status = switch_file_seek(context->fd, whence, &samples); + // if (status == SWITCH_STATUS_SUCCESS) { + // handle->pos += samples; + // } + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "unsupported seek\n"); + return SWITCH_STATUS_FALSE; +} + +static switch_status_t ffmpeg_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 ffmpeg_write(switch_file_handle_t *handle, void *data, size_t *len) +{ + ffmpeg_context *context = handle->private_info; + switch_status_t status = SWITCH_STATUS_SUCCESS; + AVFormatContext *avctx = context->avctx; + AVFrame *aframe = context->aframe; + AVStream *audio_st = context->audio_st; + AVCodecContext *codec = audio_st->codec; + AVPacket pkt = { 0 }; + int got_packet = 0; + + aframe->nb_samples = 160; //fixme + + avcodec_fill_audio_frame(aframe, codec->channels, codec->sample_fmt, + (uint8_t *)data, + (*len) * codec->channels, + 1); + + av_init_packet(&pkt); + + // avcodec_encode_audio2(codec, &pkt, aframe, &got_packet); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "got packet %d, size: %d\n", got_packet, pkt.size); + + if (!got_packet || pkt.size < 1) return status; + + pkt.stream_index = audio_st->index; + + /* Write the compressed frame to the media file. */ + switch_mutex_lock(context->mutex); + if (av_interleaved_write_frame(avctx, &pkt) != 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error while writing audio frame\n"); + switch_mutex_unlock(context->mutex); + return SWITCH_STATUS_FALSE; + } + switch_mutex_unlock(context->mutex); + + // *len = ? //fixme + return status; +} + +static switch_status_t ffmpeg_write_video(switch_file_handle_t *handle, void *data, size_t *len) +{ + uint32_t datalen = *len - 12; + switch_status_t status = SWITCH_STATUS_SUCCESS; + uint8_t *hdr = NULL; + switch_rtp_hdr_t *rtp_hdr = data; + ffmpeg_context *context = handle->private_info; + + uint8_t code[] = {0, 0, 1}; + + AVFormatContext *avctx = context->avctx; + AVStream *video_st = context->video_st; + AVPacket pkt; + + int key_frame = 0; + int ret; + + av_init_packet(&pkt); + + hdr = (uint8_t *)data + 12; + + switch_buffer_write(context->buf, &code, 3); + switch_buffer_write(context->buf, hdr, datalen); + + +#if 1 + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "read %d %02x %02x %02x %d\n", + rtp_hdr->m, *hdr, *(hdr+1), *(hdr+2), datalen); +#endif + + if (!rtp_hdr->m) return status;; // continue buffering until we get a marker + + { + int nal_type = (*hdr &0x1f); + key_frame = (nal_type == 5); + } + + pkt.pts = context->video_pts++; + + if (key_frame) { + pkt.flags |= AV_PKT_FLAG_KEY; + } else { + pkt.flags &= ~AV_PKT_FLAG_KEY; + } + + pkt.stream_index = video_st->index; + pkt.size = switch_buffer_inuse(context->buf); + switch_buffer_peek_zerocopy(context->buf, (const void **)&pkt.data); + + /* Write the compressed frame to the media file. */ + + switch_mutex_lock(context->mutex); + + if ((ret = av_write_frame(avctx, &pkt)) != 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "failed to write %d bytes, ret=%d\n", pkt.size, ret); + switch_mutex_unlock(context->mutex); + return SWITCH_STATUS_FALSE; + } + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write %d bytes, ret=%d\n", pkt.size, ret); + + switch_mutex_unlock(context->mutex); + switch_buffer_zero(context->buf); + + // *len = datalen; // FIXME + + return status; +} + +static switch_status_t ffmpeg_set_string(switch_file_handle_t *handle, switch_audio_col_t col, const char *string) +{ + return SWITCH_STATUS_FALSE; +} + +static switch_status_t ffmpeg_get_string(switch_file_handle_t *handle, switch_audio_col_t col, const char **string) +{ + return SWITCH_STATUS_FALSE; +} + +static void *SWITCH_THREAD_FUNC ffmpeg_record_video_thread(switch_thread_t *thread, void *obj) +{ + struct record_helper *eh = 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; + switch_buffer_t *buf = NULL; + + AVFormatContext *avctx = eh->avctx; + AVStream *video_st = eh->video_st; + AVPacket pkt; + + int key_frame = 0; + uint8_t *hdr; + uint64_t pts = 0; + + av_init_packet(&pkt); + + if (switch_buffer_create(switch_core_session_get_pool(eh->session), &buf, BUFFERSIZE) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "memory buffer error!\n"); + goto end; + } + + eh->up = 1; + + while (switch_channel_ready(channel) && eh->up) { + int ret; + uint8_t code[] = {0, 0, 1}; + + 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; + } + + switch_core_session_write_video_frame(session, read_frame, SWITCH_IO_FLAG_NONE, 0); + + switch_buffer_write(buf, &code, 3); + switch_buffer_write(buf, read_frame->data, read_frame->datalen); + + hdr = read_frame->data; + +#if 0 + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "read %d %02x %02x %02x %d\n", + read_frame->m, *hdr, *(hdr+1), *(hdr+2), read_frame->datalen); +#endif + + if (!read_frame->m) continue; // continue buffering until we get a marker + + { + int nal_type = (*hdr &0x1f); + key_frame = (nal_type == 5); + } + + // if (oc->coded_frame->pts != AV_NOPTS_VALUE) { + // pts = av_rescale_q(read_frame->timestamp, video_st->codec->time_base, video_st->time_base); + // pkt.pts = read_frame->timestamp; + + pkt.pts = pts++; + + if (key_frame) { + pkt.flags |= AV_PKT_FLAG_KEY; + } else { + pkt.flags &= ~AV_PKT_FLAG_KEY; + } + + pkt.stream_index = video_st->index; + pkt.size = switch_buffer_inuse(buf); + switch_buffer_peek_zerocopy(buf, (const void **)&pkt.data); + + /* Write the compressed frame to the media file. */ + + switch_mutex_lock(eh->mutex); + + if ((ret = av_write_frame(avctx, &pkt)) != 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "failed to write %d bytes, ret=%d\n", pkt.size, ret); + switch_mutex_unlock(eh->mutex); + break; + } + + switch_mutex_unlock(eh->mutex); + switch_buffer_zero(buf); + + } + + if (video_st) { + avcodec_close(video_st->codec); + } + +end: + + if (buf) switch_buffer_destroy(&buf); + + eh->up = 0; + return NULL; +} + +SWITCH_STANDARD_APP(record_ffmpeg_function) +{ + 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; + switch_mutex_t *mutex = NULL; + switch_codec_t codec; + //switch_codec_t *vid_codec; + switch_codec_implementation_t read_impl = { 0 }; + switch_dtmf_t dtmf = { 0 }; + int count = 0, sanity = 30; + + //ffmpeg + AVFormatContext *avctx; + AVOutputFormat *fmt=NULL; + AVStream *audio_st=NULL; + AVCodecContext *audio_codec; + AVFrame *avframe=NULL; + + /* Tell the channel to request a fresh vid frame */ + + switch_core_session_get_read_impl(session, &read_impl); + switch_channel_answer(channel); + switch_core_session_refresh_video(session); + + switch_channel_set_variable(channel, SWITCH_PLAYBACK_TERMINATOR_USED, ""); + + 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)); + switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "Got timeout while waiting for video"); + 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)); + switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "Channel not ready"); + return; + } + + if (read_impl.ianacode != 70) { + if (switch_core_codec_init(&codec, + "L16", + NULL, + 8000, + 20, + 1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE, + NULL, switch_core_session_get_pool(session)) == SWITCH_STATUS_SUCCESS) { + switch_core_session_set_read_codec(session, &codec); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Codec Activation Success\n"); + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Codec Activation Fail\n"); + goto end; + } + } + + avframe = av_frame_alloc(); + + if (!avframe) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "memory error!\n"); + goto end; + } + + av_register_all(); + + //avformat_alloc_output_context2(&avctx, NULL, NULL, data); + avctx = NULL; + + if (!avctx) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not deduce output format from file extension: using mpeg4.\n"); + //avformat_alloc_output_context2(&avctx, NULL, "mpeg4", data); + } + + if (!avctx) { + goto end; + } + + fmt = avctx->oformat; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "audio_codec: 0x%x video_codec: 0x%x\n", fmt->audio_codec, fmt->video_codec); + + if (fmt->audio_codec != AV_CODEC_ID_NONE) { + audio_st = add_audio_stream(avctx, fmt->audio_codec); + } + + if (switch_channel_test_flag(channel, CF_VIDEO)) { + + if (fmt->video_codec != AV_CODEC_ID_NONE) { + eh.video_st = add_video_stream(avctx, fmt->video_codec); + } + + if (!eh.video_st) { + if (!audio_st) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No audio & video stream!\n"); + goto end; + } + } + } + + audio_codec = audio_st->codec; + + /* open it */ + if (avcodec_open2(audio_codec, NULL, NULL) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "could not open audio codec\n"); + goto end; + } + +#if 1 + av_dump_format(avctx, 0, data, 1); +#endif + + /* open the output file, if needed */ + if (!(fmt->flags & AVFMT_NOFILE)) { + if (avio_open(&avctx->pb, data, AVIO_FLAG_WRITE) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not open [%s]\n", data); + goto end; + } + } + + /* Write the stream header, if any. */ + avformat_write_header(avctx, NULL); + + if (switch_channel_test_flag(channel, CF_VIDEO)) { + //vid_codec = switch_core_session_get_video_read_codec(session); + + switch_mutex_init(&mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session)); + eh.mutex = mutex; + eh.avctx = avctx; + 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, ffmpeg_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_SINGLE_READ, 0); + + if (switch_channel_test_flag(channel, CF_BREAK)) { + switch_channel_clear_flag(channel, CF_BREAK); + eh.up = 0; + break; + } + + switch_ivr_parse_all_events(session); + + //check for dtmf interrupts + if (switch_channel_has_dtmf(channel)) { + const char * terminators = switch_channel_get_variable(channel, SWITCH_PLAYBACK_TERMINATORS_VARIABLE); + switch_channel_dequeue_dtmf(channel, &dtmf); + + if (terminators && !strcasecmp(terminators, "none")) + { + terminators = NULL; + } + + if (terminators && strchr(terminators, dtmf.digit)) { + + char sbuf[2] = {dtmf.digit, '\0'}; + switch_channel_set_variable(channel, SWITCH_PLAYBACK_TERMINATOR_USED, sbuf); + eh.up = 0; + break; + } + } + + if (!SWITCH_READ_ACCEPTABLE(status)) { + eh.up = 0; + break; + } + + if (switch_test_flag(read_frame, SFF_CNG)) { + continue; + } + + switch_core_session_write_frame(session, read_frame, SWITCH_IO_FLAG_NONE, 0); + + avframe->nb_samples = 160; + + avcodec_fill_audio_frame(avframe, audio_codec->channels, audio_codec->sample_fmt, + (uint8_t *)read_frame->data, + read_frame->datalen * audio_codec->channels, + 1); + + { + AVPacket pkt = { 0 }; + int got_packet; + + av_init_packet(&pkt); + + avcodec_encode_audio2(audio_codec, &pkt, avframe, &got_packet); + + // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "got packet %d, size: %d\n", got_packet, pkt.size); + + if (!got_packet || pkt.size < 1) continue; + + pkt.stream_index = audio_st->index; + + /* Write the compressed frame to the media file. */ + switch_mutex_lock(eh.mutex); + if (av_interleaved_write_frame(avctx, &pkt) != 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error while writing audio frame\n"); + switch_mutex_unlock(eh.mutex); + break; + } + switch_mutex_unlock(eh.mutex); + + } + } + + av_write_trailer(avctx); + + switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "OK"); + +end: + + switch_core_session_set_read_codec(session, NULL); + + if (switch_core_codec_ready(&codec)) { + switch_core_codec_destroy(&codec); + } + + if (eh.up) { + while (eh.up) { + switch_cond_next(); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "waiting video thread to stop\n"); + } + } + + if (audio_st) avcodec_close(audio_st->codec); + + { + int i; + for (i = 0; i < avctx->nb_streams; i++) { + av_freep(&avctx->streams[i]->codec); + av_freep(&avctx->streams[i]); + } + } + + if (!(fmt->flags & AVFMT_NOFILE)) { + /* Close the output file. */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "closing file [%s]\n", data); + avio_close(avctx->pb); + } + + if (avctx) av_free(avctx); + if (avframe) av_free(avframe); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "==== done ========= done ==== \n"); + +} + +#define IP "192.168.1.1" + +static int write_packet(void *opaque, uint8_t *buf, int size) { + struct record_helper *eh = opaque; + switch_size_t bytes = size; + int i = -1; + switch_rtp_hdr_t *hdr; + switch_status_t status; + + while (size > 188) { + if (size > 188 * 7) { + bytes = 188 * 7; + size -= 188 * 7; + i++; + } else { + bytes = size; + i++; + size = 0; + } + + hdr = (switch_rtp_hdr_t *)(buf + 188 * 7 * i - 12); + memset(hdr, 0, 12); + + hdr->ssrc = 0x1111; + hdr->ts = switch_time_now(); + hdr->ts = htonl(hdr->ts); + hdr->version = 0x2; + hdr->seq = eh->seq++; + hdr->pt = 33; + + if (bytes < 188 * 7) hdr->m = 1; + + bytes += 12; + + status = switch_socket_sendto(eh->sock, eh->sock_addr, 0, (void *)hdr , &bytes); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "========== sending %ld %d\n", bytes, status); + + } + + return size; +} + +static void *SWITCH_THREAD_FUNC stream_ts_thread(switch_thread_t *thread, void *obj) +{ + struct record_helper *eh = 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; + switch_buffer_t *buf; + + AVFormatContext *oc; + AVOutputFormat *fmt; + AVStream *video_st = NULL; // better to be NULL than uninitialised + AVPacket pkt; + uint8_t *buf_callback = (uint8_t *)av_malloc(BUFFERSIZE); + AVIOContext *io; + + // uint8_t *fragment_type; + int key_frame = 0; + // int nal_type; + uint8_t *hdr; + uint64_t pts = 0; + + eh->seq = 0; + + io = avio_alloc_context(buf_callback + 12, BUFFERSIZE, 1, eh, NULL, write_packet, NULL); + + if (io == NULL) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "IO is NULL\n"); + return NULL; + } + + if (switch_socket_create(&eh->sock, AF_INET, SOCK_DGRAM, 0, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Socket Error 1\n"); + return NULL; + } + + if (switch_sockaddr_info_get(&eh->sock_addr, IP, SWITCH_UNSPEC, + 11000, 0, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Socket Error 3\n"); + return NULL; + } + + if (switch_buffer_create(switch_core_session_get_pool(eh->session), &buf, 65536) != SWITCH_STATUS_SUCCESS) return NULL; + + av_register_all(); + + av_init_packet(&pkt); + + + fmt = av_guess_format(NULL, "filename.ts", NULL); // CODEC_ID_H264 -> mp4, mov; CODEC_ID_THEORA -> ogg; CODEC_ID_MPEG4 -> mpegts, avi; CODEC_ID_VP8 -> webm + + if (!fmt) { + printf("Could not deduce output format from file extension: using MP4.\n"); + fmt = av_guess_format("mpeg4", NULL, NULL); + } + if (!fmt) { + printf("av_guess_format Error!\n"); + return NULL; + } + + + oc = avformat_alloc_context(); + + if (!oc) { + printf("Could not deduce output format from file extension: using MP4.\n"); + + return NULL; + } + + oc->pb = io; + oc->oformat = fmt; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "x:%d y:%d\n", AV_CODEC_ID_NONE, fmt->video_codec); + + if (fmt->video_codec != AV_CODEC_ID_NONE) { + video_st = add_video_stream(oc, fmt->video_codec); + } + + // av_dump_format(oc, 0, filename, 1); + + /* open the output file, if needed */ + // if (!(fmt->flags & AVFMT_NOFILE)) { + // if (avio_open(&oc->pb, filename, AVIO_FLAG_WRITE) < 0) { + // fprintf(stderr, "Could not open '%s'\n", filename); + // return NULL; + // } + // } + + /* Write the stream header, if any. */ + avformat_write_header(oc, NULL); + + eh->up = 1; + + + while (switch_channel_ready(channel) && eh->up) { + int ret; + uint8_t code[] = {0, 0, 1}; + + 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; + } + + switch_core_session_write_video_frame(session, read_frame, SWITCH_IO_FLAG_NONE, 0); + + switch_buffer_write(buf, &code, 3); + switch_buffer_write(buf, read_frame->data, read_frame->datalen); + + hdr = read_frame->data; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "read %d %02x %02x %02x %d\n", + read_frame->m, *hdr, *(hdr+1), *(hdr+2), read_frame->datalen); + + if (!read_frame->m) continue; // continue buffering until we get a marker + + { + int nal_type = (*hdr &0x1f); + key_frame = (nal_type == 5); + } + + // if (oc->coded_frame->pts != AV_NOPTS_VALUE) { + // pts = av_rescale_q(read_frame->timestamp, video_st->codec->time_base, video_st->time_base); + // pkt.pts = read_frame->timestamp; + + pkt.pts = pts; + pts+=1; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "pts %llu\n", (long long unsigned int)pkt.pts); + // } + + if (key_frame) { + pkt.flags |= AV_PKT_FLAG_KEY; + } else { + pkt.flags &= ~AV_PKT_FLAG_KEY; + } + + pkt.stream_index = video_st->index; + pkt.size = switch_buffer_inuse(buf); + switch_buffer_peek_zerocopy(buf, (const void **)&pkt.data); + + /* Write the compressed frame to the media file. */ + // ret = av_interleaved_write_frame(oc, &pkt); + ret = av_write_frame(oc, &pkt); + + switch_buffer_zero(buf); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write: %d, ret %d\n", pkt.size, ret); + + } + + av_write_trailer(oc); + + // if (0 && video_st) close_video(oc, video_st); + + {int i; + for (i = 0; i < oc->nb_streams; i++) { + av_freep(&oc->streams[i]->codec); + av_freep(&oc->streams[i]); + }} + + if (eh->sock) { + switch_socket_close(eh->sock); + } + +switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "flags %d\n", fmt->flags); + + // if (!(fmt->flags & AVFMT_NOFILE)) { + // /* Close the output file. */ + // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "closing output file\n"); + // avio_close(oc->pb); + // } + + // av_free(oc); + + // av_free(fmt); // fixme? this will be freed with the following IO + av_free(io); + av_free(buf_callback); + switch_buffer_destroy(&buf); + eh->up = 0; + return NULL; +} + +SWITCH_STANDARD_APP(stream_ts_function) +{ + 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 *vid_codec; + switch_codec_implementation_t read_impl = { 0 }; + switch_dtmf_t dtmf = { 0 }; + int count = 0, sanity = 30; + switch_core_session_message_t msg = { 0 }; + + /* Tell the channel to request a fresh vid frame */ + msg.from = __FILE__; + msg.message_id = SWITCH_MESSAGE_INDICATE_VIDEO_REFRESH_REQ; + + switch_core_session_get_read_impl(session, &read_impl); + switch_channel_answer(channel); + switch_core_session_receive_message(session, &msg); + + switch_channel_set_variable(channel, SWITCH_PLAYBACK_TERMINATOR_USED, ""); + + 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)); + switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "Got timeout while waiting for video"); + 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)); + switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "Channel not ready"); + return; + } + + if (switch_channel_test_flag(channel, CF_VIDEO)) { + //vid_codec = switch_core_session_get_video_read_codec(session); + + 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, stream_ts_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_SINGLE_READ, 0); + + if (switch_channel_test_flag(channel, CF_BREAK)) { + switch_channel_clear_flag(channel, CF_BREAK); + eh.up = 0; + break; + } + + switch_ivr_parse_all_events(session); + + //check for dtmf interrupts + if (switch_channel_has_dtmf(channel)) { + const char * terminators = switch_channel_get_variable(channel, SWITCH_PLAYBACK_TERMINATORS_VARIABLE); + switch_channel_dequeue_dtmf(channel, &dtmf); + + if (terminators && !strcasecmp(terminators, "none")) + { + terminators = NULL; + } + + if (terminators && strchr(terminators, dtmf.digit)) { + + char sbuf[2] = {dtmf.digit, '\0'}; + switch_channel_set_variable(channel, SWITCH_PLAYBACK_TERMINATOR_USED, sbuf); + eh.up = 0; + break; + } + } + + if (!SWITCH_READ_ACCEPTABLE(status)) { + eh.up = 0; + break; + } + + if (switch_test_flag(read_frame, SFF_CNG)) { + continue; + } + + switch_core_session_write_frame(session, read_frame, SWITCH_IO_FLAG_NONE, 0); + } + + switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "OK"); + + // end: + + if (eh.up) { + while (eh.up) { + switch_cond_next(); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "waiting video thread to be end\n"); + } + } + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "==== done ========= ts done ==== \n"); + +} + +#endif + +/* codec interface */ + +#define H264_NALU_BUFFER_SIZE 65536 +#define MAX_NALUS 100 + +typedef struct our_h264_nalu_s +{ + const uint8_t *start; + const uint8_t *eat; + uint32_t len; +} our_h264_nalu_t; + +typedef struct h264_codec_context_s { + switch_buffer_t *nalu_buffer; + AVCodec *decoder; + AVCodec *encoder; + AVCodecContext *decoder_ctx; + AVCodecContext *encoder_ctx; + int got_pps; /* if pps packet received */ + int64_t pts; + int got_encoded_output; + AVFrame *encoder_avframe; + AVPacket encoder_avpacket; + our_h264_nalu_t nalus[MAX_NALUS]; + int nalu_current_index; + switch_size_t last_received_timestamp; + switch_bool_t last_received_complete_picture; + switch_image_t *img; + +#ifdef H264_CODEC_USE_LIBX264 + /*x264*/ + + x264_t *x264_handle; + x264_param_t x264_params; + x264_nal_t *x264_nals; + int x264_nal_count; + int cur_nalu_index; + int need_key_frame; +#endif + +} h264_codec_context_t; + +#ifdef H264_CODEC_USE_LIBX264 + +#define RTP_SLICE_SIZE SWITCH_DEFAULT_VIDEO_SIZE //NALU Slice Size + +static switch_status_t init_x264(h264_codec_context_t *context, uint32_t width, uint32_t height) +{ + x264_t *xh = context->x264_handle; + x264_param_t *xp = &context->x264_params; + int ret = 0; + + if (xh) { + xp->i_width = width; + xp->i_height = height; + ret = x264_encoder_reconfig(xh, xp); + + if (ret == 0) return SWITCH_STATUS_SUCCESS; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Cannot Reset error:%d\n", ret); + return SWITCH_STATUS_FALSE; + } + + x264_param_default(xp); + // xp->i_level_idc = 31; // baseline + // CPU Flags + xp->i_threads = 1;//X264_SYNC_LOOKAHEAD_AUTO; + // xp->i_lookahead_threads = X264_SYNC_LOOKAHEAD_AUTO; + // Video Properties + xp->i_width = width; + xp->i_height = height; + xp->i_frame_total = 0; + xp->i_keyint_max = FPS * 10; + // Bitstream parameters + xp->i_bframe = 0; + xp->i_frame_reference = 0; + // xp->b_open_gop = 0; + // xp->i_bframe_pyramid = 0; + // xp->i_bframe_adaptive = X264_B_ADAPT_TRELLIS; + + //xp->vui.i_sar_width = 1080; + //xp->vui.i_sar_height = 720; + // xp->i_log_level = X264_LOG_DEBUG; + xp->i_log_level = X264_LOG_NONE; + // Rate control Parameters + xp->rc.i_bitrate = 378;//kbps + // Muxing parameters + xp->i_fps_den = 1; + xp->i_fps_num = FPS; + xp->i_timebase_den = xp->i_fps_num; + xp->i_timebase_num = xp->i_fps_den; + xp->i_slice_max_size = RTP_SLICE_SIZE; + //Set Profile 0=baseline other than 1=MainProfile + x264_param_apply_profile(xp, x264_profile_names[0]); + xh = x264_encoder_open(xp); + + if (!xh) return SWITCH_STATUS_FALSE; + + // copy params back to xp; + x264_encoder_parameters(xh, xp); + context->x264_handle = xh; + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t nalu_slice(h264_codec_context_t *context, switch_frame_t *frame) +{ + int nalu_len; + uint8_t *buffer; + int start_code_len = 3; + x264_nal_t *nal = &context->x264_nals[context->cur_nalu_index]; + switch_status_t status = SWITCH_STATUS_SUCCESS; + + //*flag &= ~SFF_MARKER; + frame->m = 0; + + if (context->cur_nalu_index >= context->x264_nal_count) { + frame->datalen = 0; + frame->m = 0; + context->cur_nalu_index = 0; + return SWITCH_STATUS_NOTFOUND; + } + + if (nal->b_long_startcode) start_code_len++; + + nalu_len = nal->i_payload - start_code_len; + buffer = nal->p_payload + start_code_len; + + // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "nalu:%d/%d nalu_len:%d\n", + // context->cur_nalu_index, context->x264_nal_count, nalu_len); + + switch_assert(nalu_len > 0); + + // if ((*buffer & 0x1f) == 7) switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Got SPS\n"); + + memcpy(frame->data, buffer, nalu_len); + frame->datalen = nalu_len; + if (context->cur_nalu_index == context->x264_nal_count - 1) { + frame->m = 1; + } else { + status = SWITCH_STATUS_MORE_DATA; + } + + context->cur_nalu_index++; + + return status; +} + +#endif + +static uint8_t ff_input_buffer_padding[FF_INPUT_BUFFER_PADDING_SIZE] = { 0 }; + +static void buffer_h264_nalu(h264_codec_context_t *context, switch_frame_t *frame) +{ + // uint8_t nalu_idc = 0; + uint8_t nalu_type = 0; + uint8_t *data = frame->data; + uint8_t nalu_hdr = *data; + uint8_t sync_bytes[] = {0, 0, 0, 1}; + switch_buffer_t *buffer = context->nalu_buffer; + + if (!frame) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "no frame in codec!!\n"); + return; + } + + // nalu_idc = (nalu_hdr & 0x60) >> 5; + nalu_type = nalu_hdr & 0x1f; + + // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%02x %d\n", nalu_hdr, frame->datalen); + + if (!context->got_pps && nalu_type != 7) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "waiting pps\n"); + switch_set_flag(frame, SFF_WAIT_KEY_FRAME); + return; + } + + if (!context->got_pps) context->got_pps = 1; + + switch_buffer_write(buffer, sync_bytes, sizeof(sync_bytes)); + switch_buffer_write(buffer, frame->data, frame->datalen); + + if (frame->m) { + switch_buffer_write(buffer, ff_input_buffer_padding, sizeof(ff_input_buffer_padding)); + } +} + +#ifndef H264_CODEC_USE_LIBX264 + +static switch_status_t consume_nalu(h264_codec_context_t *context, void *data, uint32_t *len, uint32_t *flag) +{ + our_h264_nalu_t *nalu = &context->nalus[context->nalu_current_index]; + + if (!nalu->start) { + *len = 0; + *flag = 1; + if (context->encoder_avpacket.size > 0) av_free_packet(&context->encoder_avpacket); + if (context->encoder_avframe->data) av_freep(&context->encoder_avframe->data[0]); + context->nalu_current_index = 0; + return SWITCH_STATUS_SUCCESS; + } + + assert(nalu->len); + + if (nalu->len <= SLICE_SIZE) { + uint8_t nalu_hdr = *(uint8_t *)(nalu->start); + uint8_t nalu_type = nalu_hdr & 0x1f; + + memcpy(data, nalu->start, nalu->len); + *len = nalu->len; + *flag = (nalu_type == 6 || nalu_type == 7 || nalu_type == 8) ? 0 : 1; + context->nalu_current_index++; + return SWITCH_STATUS_SUCCESS; + } else { + uint8_t nalu_hdr = *(uint8_t *)(nalu->start); + uint8_t nri = nalu_hdr & 0x60; + uint8_t nalu_type = nalu_hdr & 0x1f; + int left = nalu->len - (nalu->eat - nalu->start); + uint8_t *p = data; + + if (left <= (1400 - 2)) { + p[0] = nri | 28; // FU-A + p[1] = 0x40 | nalu_type; + memcpy(p+2, nalu->eat, left); + nalu->eat += left; + *len = left + 2; + *flag = 1; + context->nalu_current_index++; + return SWITCH_STATUS_SUCCESS; + } else { + uint8_t start = nalu->start == nalu->eat ? 0x80 : 0; + + p[0] = nri | 28; // FU-A + p[1] = start | nalu_type; + if (start) nalu->eat++; + memcpy(p+2, nalu->eat, 1400 - 2); + nalu->eat += (1400 - 2); + *len = 1400; + *flag = 0; + return SWITCH_STATUS_SUCCESS; + } + } +} + +static switch_status_t open_encoder(h264_codec_context_t *context, uint32_t width, uint32_t height) +{ + + context->encoder_ctx->width = width; + context->encoder_ctx->height = height; + + if (avcodec_is_open(context->encoder_ctx)) avcodec_close(context->encoder_ctx); + + if (avcodec_open2(context->encoder_ctx, context->encoder, NULL) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not open codec\n"); + return SWITCH_STATUS_FALSE; + } + + return SWITCH_STATUS_SUCCESS; +} +#endif + +static switch_status_t switch_h264_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 { + h264_codec_context_t *context = NULL; + if (codec->fmtp_in) { + codec->fmtp_out = switch_core_strdup(codec->memory_pool, codec->fmtp_in); + } + + context = switch_core_alloc(codec->memory_pool, sizeof(h264_codec_context_t)); + switch_assert(context); + memset(context, 0, sizeof(*context)); + + if (decoding) { + context->decoder = avcodec_find_decoder(AV_CODEC_ID_H264); + + if (!context->decoder) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot find codec AV_CODEC_ID_H264\n"); + goto error; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "codec: id=%d %s\n", context->decoder->id, context->decoder->long_name); + + context->decoder_ctx = avcodec_alloc_context3(context->decoder); + + if (avcodec_open2(context->decoder_ctx, context->decoder, NULL) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error openning codec\n"); + goto error; + } + } + + if (encoding) { + context->encoder = avcodec_find_encoder(AV_CODEC_ID_H264); + if (!context->encoder) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot find AV_CODEC_ID_H264 decoder\n"); + goto skip; + } + + context->encoder_ctx = avcodec_alloc_context3(context->encoder); + if (!context->encoder_ctx) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not allocate video encoder context\n"); + goto error; + } + + context->encoder_ctx->bit_rate = 400000; + context->encoder_ctx->width = 352; + context->encoder_ctx->height = 288; + /* frames per second */ + context->encoder_ctx->time_base= (AVRational){1, FPS}; + context->encoder_ctx->gop_size = FPS * 3; /* emit one intra frame every 3 seconds */ + context->encoder_ctx->max_b_frames = 0; + context->encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P; + // context->encoder_ctx->thread_count = 1; + // context->encoder_ctx->rtp_payload_size = RTP_SLICE_SIZE; + av_opt_set(context->encoder_ctx->priv_data, "preset", "fast", 0); + } + +skip: + + switch_buffer_create_dynamic(&(context->nalu_buffer), H264_NALU_BUFFER_SIZE, H264_NALU_BUFFER_SIZE * 8, 0); + codec->private_info = context; + + return SWITCH_STATUS_SUCCESS; + } + +error: + // todo, do some clean up + return SWITCH_STATUS_FALSE; +} + +#ifndef H264_CODEC_USE_LIBX264 + +static void __attribute__((unused)) fill_avframe(AVFrame *pict, switch_image_t *img) +{ + int i; + uint8_t *y = img->planes[0]; + uint8_t *u = img->planes[1]; + uint8_t *v = img->planes[2]; + + /* Y */ + for (i = 0; i < pict->height; i++) { + memcpy(&pict->data[0][i * pict->linesize[0]], y + i * img->stride[0], pict->width); + } + + //U V + for(i = 0; i < pict->height / 2; i++) { + memcpy(&pict->data[1][i * pict->linesize[1]], u + i * img->stride[1], pict->width / 2); + memcpy(&pict->data[2][i * pict->linesize[2]], v + i * img->stride[2], pict->width / 2); + } + +} + +// switch_image_t *img, +// void *encoded_data, uint32_t *encoded_data_len, +// unsigned int *flag) + +static switch_status_t switch_h264_encode(switch_codec_t *codec, switch_frame_t *frame) +{ + h264_codec_context_t *context = (h264_codec_context_t *)codec->private_info; + AVCodecContext *avctx= context->encoder_ctx; + int ret; + int *got_output = &context->got_encoded_output; + AVFrame *avframe; + AVPacket *pkt = &context->encoder_avpacket; + uint32_t width = 0; + uint32_t height = 0; + switch_image_t *img = frame->img; + void *encoded_data = frame->data; + uint32_t *encoded_data_len = frame->datalen; + unsigned int *flag = &frame->flags; + + if (*encoded_data_len < SWITCH_DEFAULT_VIDEO_SIZE) return SWITCH_STATUS_FALSE; + + if (frame->flags & SFF_SAME_IMAGE) { + // read from buffered + return consume_nalu(context, encoded_data, encoded_data_len, flag); + } + + width = img->d_w; + height = img->d_h; + + if (!avcodec_is_open(avctx)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "initializing encoder %dx%d\n", width, height); + open_encoder(context, width, height); + } + + if (avctx->width != width || avctx->height != height) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "picture size changed from %dx%d to %dx%d, reinitializing encoder", + avctx->width, avctx->height, width, height); + open_encoder(context, width, height); + } + + av_init_packet(pkt); + pkt->data = NULL; // packet data will be allocated by the encoder + pkt->size = 0; + + if (!context->encoder_avframe) context->encoder_avframe = avcodec_alloc_frame(); + + avframe = context->encoder_avframe; + + if (!avframe) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error allocate frame!\n"); + goto error; + } + + avframe->format = avctx->pix_fmt; + avframe->width = avctx->width; + avframe->height = avctx->height; + + /* the image can be allocated by any means and av_image_alloc() is + * just the most convenient way if av_malloc() is to be used */ + ret = av_image_alloc(avframe->data, avframe->linesize, avctx->width, avctx->height, avctx->pix_fmt, 32); + if (ret < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not allocate raw picture buffer\n"); + goto error; + } + + switch_assert(encoded_data); + + if (*got_output) { // Could be more delayed frames + ret = avcodec_encode_video2(avctx, pkt, NULL, got_output); + if (ret < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Encoding Error %d\n", ret); + goto error; + } + + if (*got_output) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Encoded frame %llu (size=%5d) nalu_type=0x%x %d\n", context->pts, pkt->size, *((uint8_t *)pkt->data +4), *got_output); + goto process; + } + } + + fill_avframe(avframe, img); + + avframe->pts = context->pts++; + + /* encode the image */ + ret = avcodec_encode_video2(avctx, pkt, avframe, got_output); + + if (ret < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Encoding Error %d\n", ret); + goto error; + } + +process: + + if (*got_output) { + const uint8_t *p = pkt->data; + int i = 0; + + /* split into nalus */ + memset(context->nalus, 0, sizeof(context->nalus)); + + while ((p = ff_avc_find_startcode(p, pkt->data+pkt->size)) < (pkt->data + pkt->size)) { + if (!context->nalus[i].start) { + while (!(*p++)) ; /* eat the sync bytes, what ever 0 0 1 or 0 0 0 1 */ + context->nalus[i].start = p; + context->nalus[i].eat = p; + } else { + context->nalus[i].len = p - context->nalus[i].start; + while (!(*p++)) ; /* eat the sync bytes, what ever 0 0 1 or 0 0 0 1 */ + i++; + context->nalus[i].start = p; + context->nalus[i].eat = p; + } + if (i >= MAX_NALUS - 2) break; + } + + context->nalus[i].len = p - context->nalus[i].start; + context->nalu_current_index = 0; + return consume_nalu(context, encoded_data, encoded_data_len, flag); + } + +error: + *encoded_data_len = 0; + return SWITCH_STATUS_FALSE; +} + +#endif + +#ifdef H264_CODEC_USE_LIBX264 + +static switch_status_t switch_h264_encode(switch_codec_t *codec, + switch_frame_t *frame) + //switch_image_t *img, + //void *encoded_data, uint32_t *encoded_data_len, + //unsigned int *flag) +{ + h264_codec_context_t *context = (h264_codec_context_t *)codec->private_info; + uint32_t width = 0; + uint32_t height = 0; + x264_picture_t pic = { 0 }, pic_out = { 0 }; + int result; + switch_image_t *img = frame->img; + void *encoded_data = frame->data; + uint32_t *encoded_data_len = &frame->datalen; + unsigned int *flag = &frame->flags; + + if (*flag & SFF_WAIT_KEY_FRAME) context->need_key_frame = 1; + + //if (*encoded_data_len < SWITCH_DEFAULT_VIDEO_SIZE) return SWITCH_STATUS_FALSE; + + if (!context) return SWITCH_STATUS_FALSE; + + if (!context->x264_handle) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "initializing x264 handle %dx%d\n", width, height); + init_x264(context, width, height); + } + + if (frame->flags & SFF_SAME_IMAGE) { + return nalu_slice(context, frame); + } + + width = img->d_w; + height = img->d_h; + + if (context->x264_params.i_width != width || context->x264_params.i_height != height) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "picture size changed from %dx%d to %dx%d, reinitializing encoder", + context->x264_params.i_width, context->x264_params.i_width, width, height); + init_x264(context, width, height); + } + + switch_assert(encoded_data); + + x264_picture_init(&pic); + pic.img.i_csp = X264_CSP_I420; + pic.img.i_plane = 3; + pic.img.i_stride[0] = img->stride[0]; + pic.img.i_stride[1] = img->stride[1]; + pic.img.i_stride[2] = img->stride[2]; + pic.img.plane[0] = img->planes[0]; + pic.img.plane[1] = img->planes[1]; + pic.img.plane[2] = img->planes[2]; + // pic.i_pts = (context->pts++); + // pic.i_pts = (context->pts+=90000/FPS); + pic.i_pts = 0; + + if (context->need_key_frame) { + // pic->i_type = X264_TYPE_KEYFRAME; + pic.i_type = X264_TYPE_IDR; + context->need_key_frame = 0; + } + + result = x264_encoder_encode(context->x264_handle, &context->x264_nals, &context->x264_nal_count, &pic, &pic_out); + + if (result < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "encode error\n"); + goto error; + } + + // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "encode result:%d nal_count:%d daylayed: %d, max_delayed: %d\n", result, context->x264_nal_count, x264_encoder_delayed_frames(context->x264_handle), x264_encoder_maximum_delayed_frames(context->x264_handle)); + + if (0) { //debug + int i; + x264_nal_t *nals = context->x264_nals; + for (i = 0; i < context->x264_nal_count; i++) { + // int start_code_len = 3 + nals[i].b_long_startcode; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "encoded: %d %d %d %d| %d %d %d %x %x %x %x %x %x\n", + nals[i].i_type, nals[i].i_ref_idc, nals[i].i_payload, nals[i].b_long_startcode, *(nals[i].p_payload), *(nals[i].p_payload + 1), *(nals[i].p_payload + 2), *(nals[i].p_payload+3), *(nals[i].p_payload + 4), *(nals[i].p_payload + 5), *(nals[i].p_payload + 6), *(nals[i].p_payload + 7), *(nals[i].p_payload + 8)); + } + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "encoder output dts:%ld\n", (long)pic_out.i_dts); + } + + context->cur_nalu_index = 0; + return nalu_slice(context, frame); + +error: + + *encoded_data_len = 0; + return SWITCH_STATUS_NOTFOUND; +} + +#endif + +static switch_status_t switch_h264_decode(switch_codec_t *codec, switch_frame_t *frame) +{ + h264_codec_context_t *context = (h264_codec_context_t *)codec->private_info; + AVCodecContext *avctx= context->decoder_ctx; + + switch_assert(frame); + + // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "len: %d ts: %u mark:%d\n", frame->datalen, ntohl(frame->timestamp), frame->m); + + //if (context->last_received_timestamp && context->last_received_timestamp != frame->timestamp && + // (!frame->m) && (!context->last_received_complete_picture)) { + // possible packet loss + // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Packet Loss, skip privousely received packets\n"); + // switch_buffer_zero(context->nalu_buffer); + //} + + context->last_received_timestamp = frame->timestamp; + context->last_received_complete_picture = frame->m ? SWITCH_TRUE : SWITCH_FALSE; + + buffer_h264_nalu(context, frame); + + if (frame->m) { + uint32_t size = switch_buffer_inuse(context->nalu_buffer); + AVPacket pkt = { 0 }; + AVFrame *picture; + int got_picture = 0; + int decoded_len; + + if (size > FF_INPUT_BUFFER_PADDING_SIZE) { + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + switch_buffer_peek_zerocopy(context->nalu_buffer, (const void **)&pkt.data); + + pkt.size = size - FF_INPUT_BUFFER_PADDING_SIZE; + picture = av_frame_alloc(); + assert(picture); + decoded_len = avcodec_decode_video2(avctx, picture, &got_picture, &pkt); + + // switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "buffer: %d got pic: %d len: %d [%dx%d]\n", size, got_picture, decoded_len, avctx->width, avctx->height); + + if (got_picture && decoded_len > 0) { + int width = avctx->width; + int height = avctx->height; + + if (!context->img) { + context->img = switch_img_wrap(NULL, SWITCH_IMG_FMT_I420, width, height, 0, picture->data[0]); + assert(context->img); + } + context->img->w = picture->linesize[0]; + context->img->h = picture->linesize[1]; + context->img->d_w = width; + context->img->d_h = height; + context->img->planes[0] = picture->data[0]; + context->img->planes[1] = picture->data[1]; + context->img->planes[2] = picture->data[2]; + context->img->stride[0] = picture->linesize[0]; + context->img->stride[1] = picture->linesize[1]; + context->img->stride[2] = picture->linesize[2]; + + frame->img = context->img; + + } + + av_frame_free(&picture); + av_free_packet(&pkt); + } + + switch_buffer_zero(context->nalu_buffer); + return SWITCH_STATUS_SUCCESS; + } + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t switch_h264_control(switch_codec_t *codec, + switch_codec_control_command_t cmd, + switch_codec_control_type_t ctype, + void *cmd_data, + switch_codec_control_type_t *rtype, + void **ret_data) { + + + + h264_codec_context_t *context = (h264_codec_context_t *)codec->private_info; + + switch(cmd) { + case SCC_VIDEO_REFRESH: + context->need_key_frame = 1; + break; + default: + break; + } + + return SWITCH_STATUS_SUCCESS; +} + + +static switch_status_t switch_h264_destroy(switch_codec_t *codec) +{ + h264_codec_context_t *context = (h264_codec_context_t *)codec->private_info; + + if (!context) return SWITCH_STATUS_SUCCESS; + + switch_buffer_destroy(&context->nalu_buffer); + if (context->decoder_ctx) { + if (avcodec_is_open(context->decoder_ctx)) avcodec_close(context->decoder_ctx); + av_free(context->decoder_ctx); + } + + if (context->img) switch_img_free(context->img); + + if (context->encoder_ctx) { + if (avcodec_is_open(context->encoder_ctx)) avcodec_close(context->encoder_ctx); + av_free(context->encoder_ctx); + } + + if (context->encoder_avframe) { + av_frame_free(&context->encoder_avframe); + } + +#ifdef H264_CODEC_USE_LIBX264 + if (context->x264_handle) { + x264_encoder_close(context->x264_handle); + } +#endif + + return SWITCH_STATUS_SUCCESS; +} + +/* end of codec interface */ + + +/* API interface */ + +static char get_media_type_char(enum AVMediaType type) +{ + switch (type) { + case AVMEDIA_TYPE_VIDEO: return 'V'; + case AVMEDIA_TYPE_AUDIO: return 'A'; + case AVMEDIA_TYPE_DATA: return 'D'; + case AVMEDIA_TYPE_SUBTITLE: return 'S'; + case AVMEDIA_TYPE_ATTACHMENT:return 'T'; + default: return '?'; + } +} + +static const AVCodec *next_codec_for_id(enum AVCodecID id, const AVCodec *prev, + int encoder) +{ + while ((prev = av_codec_next(prev))) { + if (prev->id == id && + (encoder ? av_codec_is_encoder(prev) : av_codec_is_decoder(prev))) + return prev; + } + return NULL; +} + +static int compare_codec_desc(const void *a, const void *b) +{ + const AVCodecDescriptor * const *da = a; + const AVCodecDescriptor * const *db = b; + + return (*da)->type != (*db)->type ? (*da)->type - (*db)->type : + strcmp((*da)->name, (*db)->name); +} + +static unsigned get_codecs_sorted(const AVCodecDescriptor ***rcodecs) +{ + const AVCodecDescriptor *desc = NULL; + const AVCodecDescriptor **codecs; + unsigned nb_codecs = 0, i = 0; + + while ((desc = avcodec_descriptor_next(desc))) + nb_codecs++; + if (!(codecs = av_malloc(nb_codecs * sizeof(*codecs)))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "MEM Error!\n"); + return 0; + } + desc = NULL; + while ((desc = avcodec_descriptor_next(desc))) + codecs[i++] = desc; + switch_assert(i == nb_codecs); + qsort(codecs, nb_codecs, sizeof(*codecs), compare_codec_desc); + *rcodecs = codecs; + return nb_codecs; +} + +static void print_codecs_for_id(switch_stream_handle_t *stream, enum AVCodecID id, int encoder) +{ + const AVCodec *codec = NULL; + + stream->write_function(stream, " (%s: ", encoder ? "encoders" : "decoders"); + + while ((codec = next_codec_for_id(id, codec, encoder))) + stream->write_function(stream, "%s ", codec->name); + + stream->write_function(stream, ")"); +} + +SWITCH_STANDARD_API(ffmpeg_api_function) +{ + const AVCodecDescriptor **codecs = NULL; + unsigned i, nb_codecs = get_codecs_sorted(&codecs); + + stream->write_function(stream, "===============================================:\n" + " V..... = Video\n" + " A..... = Audio\n" + " S..... = Subtitle\n" + " .F.... = Frame-level multithreading\n" + " ..S... = Slice-level multithreading\n" + " ...X.. = Codec is experimental\n" + " ....B. = Supports draw_horiz_band\n" + " .....D = Supports direct rendering method 1\n" + " ----------------------------------------------\n\n"); + + for (i = 0; i < nb_codecs; i++) { + const AVCodecDescriptor *desc = codecs[i]; + const AVCodec *codec = NULL; + + stream->write_function(stream, " "); + stream->write_function(stream, avcodec_find_decoder(desc->id) ? "D" : "."); + stream->write_function(stream, avcodec_find_encoder(desc->id) ? "E" : "."); + + stream->write_function(stream, "%c", get_media_type_char(desc->type)); + stream->write_function(stream, (desc->props & AV_CODEC_PROP_INTRA_ONLY) ? "I" : "."); + stream->write_function(stream, (desc->props & AV_CODEC_PROP_LOSSY) ? "L" : "."); + stream->write_function(stream, (desc->props & AV_CODEC_PROP_LOSSLESS) ? "S" : "."); + + stream->write_function(stream, " %-20s %s", desc->name, desc->long_name ? desc->long_name : ""); + + /* print decoders/encoders when there's more than one or their + * names are different from codec name */ + while ((codec = next_codec_for_id(desc->id, codec, 0))) { + if (strcmp(codec->name, desc->name)) { + print_codecs_for_id(stream ,desc->id, 0); + break; + } + } + codec = NULL; + while ((codec = next_codec_for_id(desc->id, codec, 1))) { + if (strcmp(codec->name, desc->name)) { + print_codecs_for_id(stream, desc->id, 1); + break; + } + } + + stream->write_function(stream, "\n"); + + } + + av_free(codecs); + return SWITCH_STATUS_SUCCESS; +} + +static void log_callback(void *ptr, int level, const char *fmt, va_list vl) +{ + switch_log_level_t switch_level = SWITCH_LOG_DEBUG; + + switch(level) { + case AV_LOG_QUIET: switch_level = SWITCH_LOG_CONSOLE; break; + case AV_LOG_PANIC: switch_level = SWITCH_LOG_ERROR; break; + case AV_LOG_FATAL: switch_level = SWITCH_LOG_ERROR; break; + case AV_LOG_ERROR: switch_level = SWITCH_LOG_ERROR; break; + case AV_LOG_WARNING: switch_level = SWITCH_LOG_WARNING; break; + case AV_LOG_INFO: switch_level = SWITCH_LOG_INFO; break; + case AV_LOG_VERBOSE: switch_level = SWITCH_LOG_INFO; break; + case AV_LOG_DEBUG: switch_level = SWITCH_LOG_DEBUG; break; + default: break; + } + + switch_level = SWITCH_LOG_ERROR; // hardcoded for debug + switch_log_vprintf(SWITCH_CHANNEL_LOG, switch_level, fmt, vl); +} + +static char *supported_formats[SWITCH_MAX_CODECS] = { 0 }; + +SWITCH_MODULE_LOAD_FUNCTION(mod_ffmpeg_load) +{ + // switch_application_interface_t *app_interface; + // switch_file_interface_t *file_interface; + switch_codec_interface_t *codec_interface; + switch_api_interface_t *api_interface; + + supported_formats[0] = "mp4"; + supported_formats[1] = "ts"; + + /* 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, "H264 Video"); + switch_core_codec_add_video_implementation(pool, codec_interface, 99, "H264", NULL, + switch_h264_init, switch_h264_encode, switch_h264_decode, switch_h264_control, switch_h264_destroy); + +#if 0 + 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 = ffmpeg_open; + file_interface->file_close = ffmpeg_close; + file_interface->file_truncate = ffmpeg_truncate; + file_interface->file_read = ffmpeg_read; + file_interface->file_write = ffmpeg_write; + file_interface->file_write_video = ffmpeg_write_video; + file_interface->file_seek = ffmpeg_seek; + file_interface->file_set_string = ffmpeg_set_string; + file_interface->file_get_string = ffmpeg_get_string; + + // SWITCH_ADD_APP(app_interface, "play_ffmpeg", "play a video file", "play a video file", play_ffmpeg_function, "", SAF_NONE); + SWITCH_ADD_APP(app_interface, "record_ffmpeg", "record a video file", "record a video file", record_ffmpeg_function, "", SAF_NONE); + SWITCH_ADD_APP(app_interface, "stream_ts", "stream ts to rtp", "stream ts to rtp", stream_ts_function, "", SAF_NONE); +#endif + + SWITCH_ADD_API(api_interface, "ffmpeg", "ffmpeg", ffmpeg_api_function, ""); + + av_log_set_callback(log_callback); + av_log_set_level(AV_LOG_DEBUG); + av_register_all(); + + av_log(NULL, AV_LOG_INFO, "%s %d\n", "av_log callback installed, level=", av_log_get_level()); + + /* 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: + */