diff --git a/middleware/MediaManager/src/RecordMp4.cpp b/middleware/MediaManager/src/RecordMp4.cpp index a280281..9b3d117 100644 --- a/middleware/MediaManager/src/RecordMp4.cpp +++ b/middleware/MediaManager/src/RecordMp4.cpp @@ -41,10 +41,14 @@ void RecordMp4::UnInit(void) void RecordMp4::GetVideoStream(const void *stream, const unsigned int &length, const unsigned long long &timeStamp) { if (mRecordMp4Object) { - StreamInfo info = {.mType = STREAM_TYPE_END}; + StreamInfo info = {.mType = STREAM_TYPE_VIDEO_H264}; IGetStreamData(mRecordMp4Object, stream, length, info); } } void RecordMp4::GetAudioStream(const void *stream, const unsigned int &length, const unsigned long long &timeStamp) { + if (mRecordMp4Object) { + StreamInfo info = {.mType = STREAM_TYPE_AUDIO_G711A}; + IGetStreamData(mRecordMp4Object, stream, length, info); + } } \ No newline at end of file diff --git a/utils/MediaBase/src/FfmpegMuxStream.cpp b/utils/MediaBase/src/FfmpegMuxStream.cpp index ffe10e7..e9963a9 100644 --- a/utils/MediaBase/src/FfmpegMuxStream.cpp +++ b/utils/MediaBase/src/FfmpegMuxStream.cpp @@ -49,14 +49,23 @@ extern "C" { #define STREAM_DURATION 10.0 #define STREAM_FRAME_RATE 25 /* 25 images/s */ #define STREAM_PIX_FMT AV_PIX_FMT_YUV420P /* default pix_fmt */ -FfmpegMuxStream::FfmpegMuxStream() : mCodec(nullptr), mCodec_ctx(nullptr), mFrame(nullptr), mOc(nullptr) +FfmpegMuxStream::FfmpegMuxStream() + : mCodecVideo(nullptr), mCodecVideoContext(nullptr), mFrameVideo(nullptr), mCodecAudio(nullptr), + mCodecAudioContext(nullptr), mFrameAudio(nullptr), mOc(nullptr) { memset(&mVideoSt, 0, sizeof(mVideoSt)); memset(&mAudioSt, 0, sizeof(mAudioSt)); } StatusCode FfmpegMuxStream::OpenOutputFile(const std::string &fileName) { - InitCodec(&mCodec, &mCodec_ctx, &mFrame); + if (!InitCodecVideo(AV_CODEC_ID_H264, &mCodecVideo, &mCodecVideoContext, &mFrameVideo)) { + LogError("InitCodec failed\n"); + return CreateStatusCode(STATUS_CODE_NOT_OK); + } + if (!InitCodecAudio(AV_CODEC_ID_PCM_ALAW, &mCodecAudio, &mCodecAudioContext, &mFrameAudio)) { + LogError("InitCodec failed\n"); + return CreateStatusCode(STATUS_CODE_NOT_OK); + } int ret; AVFormatContext *oc = nullptr; int have_video = 0, have_audio = 0; @@ -73,11 +82,13 @@ StatusCode FfmpegMuxStream::OpenOutputFile(const std::string &fileName) /* Add the audio and video streams using the default format codecs * and initialize the codecs. */ if (fmt->video_codec != AV_CODEC_ID_NONE) { + LogInfo("Add video stream\n"); add_stream(&mVideoSt, oc, &video_codec, fmt->video_codec); have_video = 1; encode_video = 1; } if (fmt->audio_codec != AV_CODEC_ID_NONE) { + LogInfo("Add audio stream\n"); add_stream(&mAudioSt, oc, &audio_codec, fmt->audio_codec); have_audio = 1; encode_audio = 1; @@ -114,12 +125,26 @@ StatusCode FfmpegMuxStream::OpenOutputFile(const std::string &fileName) StatusCode FfmpegMuxStream::CloseOutputFile(void) { av_write_trailer(mOc); - av_frame_free(&mFrame); - mFrame = nullptr; - avcodec_free_context(&mCodec_ctx); - + if (mFrameVideo) { + av_frame_free(&mFrameVideo); + mFrameVideo = nullptr; + } + if (mFrameAudio) { + av_frame_free(&mFrameAudio); + mFrameAudio = nullptr; + } + if (mCodecVideoContext) { + avcodec_free_context(&mCodecVideoContext); + mCodecVideoContext = nullptr; + } + if (mCodecAudioContext) { + avcodec_free_context(&mCodecAudioContext); + mCodecAudioContext = nullptr; + } close_stream(mOc, &mVideoSt); close_stream(mOc, &mAudioSt); + memset(&mVideoSt, 0, sizeof(mVideoSt)); + memset(&mAudioSt, 0, sizeof(mAudioSt)); if (!(mOc->oformat->flags & AVFMT_NOFILE)) { /* Close the output file. */ avio_closep(&mOc->pb); @@ -128,21 +153,29 @@ StatusCode FfmpegMuxStream::CloseOutputFile(void) return CreateStatusCode(STATUS_CODE_OK); } void FfmpegMuxStream::GetStreamData(const void *data, const size_t &size, const StreamInfo &streamInfo) +{ + if (streamInfo.mType == STREAM_TYPE_VIDEO_H264) { + GetVideoStream(data, size, streamInfo); + } + if (streamInfo.mType == STREAM_TYPE_AUDIO_G711A) { + GetAudioStream(data, size, streamInfo); + } +} +void FfmpegMuxStream::GetVideoStream(const void *data, const size_t &size, const StreamInfo &streamInfo) { AVPacket *packet = nullptr; - // av_init_packet(&packet); packet = av_packet_alloc(); packet->data = (unsigned char *)data; packet->size = size; - int ret = avcodec_send_packet(mCodec_ctx, packet); + int ret = avcodec_send_packet(mCodecVideoContext, packet); if (ret < 0) { LogInfo("Error sending a packet for decoding\n"); - // av_packet_unref(packet); + av_packet_unref(packet); av_packet_free(&packet); return; } while (ret >= 0) { - ret = avcodec_receive_frame(mCodec_ctx, mFrame); + ret = avcodec_receive_frame(mCodecVideoContext, mFrameVideo); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } @@ -150,10 +183,40 @@ void FfmpegMuxStream::GetStreamData(const void *data, const size_t &size, const LogInfo("Error during decoding\n"); break; } - write_frame(mOc, mVideoSt.enc, mVideoSt.st, mFrame, mVideoSt.tmp_pkt); + write_frame(mOc, mVideoSt.enc, mVideoSt.st, mFrameVideo, mVideoSt.tmp_pkt); break; } - // av_packet_unref(packet); + av_packet_unref(packet); + av_packet_free(&packet); +} +void FfmpegMuxStream::GetAudioStream(const void *data, const size_t &size, const StreamInfo &streamInfo) +{ + return; + AVPacket *packet = nullptr; + packet = av_packet_alloc(); + packet->data = (unsigned char *)data; + packet->size = size; + int ret = avcodec_send_packet(mCodecAudioContext, packet); + if (ret < 0) { + LogInfo("Error sending a packet for decoding\n"); + av_packet_unref(packet); + av_packet_free(&packet); + return; + } + while (ret >= 0) { + ret = avcodec_receive_frame(mCodecAudioContext, mFrameAudio); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } + if (ret < 0) { + LogInfo("Error during decoding\n"); + break; + } + ConvertAudioFrame(mFrameVideo, mCodecAudioContext, &mAudioSt); + write_frame(mOc, mAudioSt.enc, mAudioSt.st, mFrameAudio, mAudioSt.tmp_pkt); + break; + } + av_packet_unref(packet); av_packet_free(&packet); } bool FfmpegMuxStream::add_stream(OutputStream *ost, AVFormatContext *oc, const AVCodec **codec, enum AVCodecID codec_id) @@ -403,35 +466,92 @@ AVFrame *FfmpegMuxStream::alloc_frame(enum AVPixelFormat pix_fmt, int width, int return frame; } -void FfmpegMuxStream::InitCodec(AVCodec **codec, AVCodecContext **codec_ctx, AVFrame **frame) +bool FfmpegMuxStream::InitCodecVideo(enum AVCodecID codecId, AVCodec **codec, AVCodecContext **codec_ctx, + AVFrame **frame) { int ret = 0; - *codec = (AVCodec *)avcodec_find_decoder(AV_CODEC_ID_H264); + *codec = (AVCodec *)avcodec_find_decoder(codecId); if (!(*codec)) { LogError("Codec not found\n"); - return; + return false; } *codec_ctx = avcodec_alloc_context3((const AVCodec *)(*codec)); if (!(*codec_ctx)) { LogError("Could not allocate codec context\n"); - return; + return false; } if ((ret = avcodec_open2(*codec_ctx, *codec, nullptr)) < 0) { char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; LogError("Could not open codec:%s\n", av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); - return; + return false; } *frame = av_frame_alloc(); if (!frame) { LogError("Could not allocate video frame\n"); - return; + return false; } + return true; +} +bool FfmpegMuxStream::InitCodecAudio(enum AVCodecID codecId, AVCodec **codec, AVCodecContext **codec_ctx, + AVFrame **frame) +{ + int ret = 0; + *codec = (AVCodec *)avcodec_find_decoder(codecId); + if (!(*codec)) { + LogError("Codec not found\n"); + return false; + } + *codec_ctx = avcodec_alloc_context3((const AVCodec *)(*codec)); + if (!(*codec_ctx)) { + LogError("Could not allocate codec context\n"); + return false; + } + /* put sample parameters */ + (*codec_ctx)->bit_rate = 64000; + + /* check that the encoder supports s16 pcm input */ + (*codec_ctx)->sample_fmt = AV_SAMPLE_FMT_S16; + if (!check_sample_fmt((*codec), (*codec_ctx)->sample_fmt)) { + fprintf(stderr, "Encoder does not support sample format %s", av_get_sample_fmt_name((*codec_ctx)->sample_fmt)); + return false; + } + + /* select other audio parameters supported by the encoder */ + (*codec_ctx)->sample_rate = select_sample_rate((*codec)); + ret = select_channel_layout((*codec), &((*codec_ctx)->ch_layout)); + if (ret < 0) { + LogError("Could not set channel layout\n"); + return false; + } + if ((ret = avcodec_open2(*codec_ctx, *codec, nullptr)) < 0) { + char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; + LogError("Could not open codec:%s\n", av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); + return false; + } + *frame = av_frame_alloc(); + if (!(*frame)) { + LogError("Could not allocate video frame\n"); + return false; + } + (*frame)->nb_samples = (*codec_ctx)->frame_size; + (*frame)->format = (*codec_ctx)->sample_fmt; + ret = av_channel_layout_copy(&((*frame)->ch_layout), &((*codec_ctx)->ch_layout)); + if (ret < 0) { + LogError("Could not copy channel layout\n"); + return false; + } + return true; } int FfmpegMuxStream::write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c, AVStream *st, AVFrame *frame, AVPacket *pkt) { int ret; - + if (c == nullptr) { + LogError("c is null\n"); + } + if (frame == nullptr) { + LogError("frame is null\n"); + } // send the frame to the encoder ret = avcodec_send_frame(c, frame); if (ret < 0) { @@ -490,4 +610,89 @@ void FfmpegMuxStream::log_packet(const AVFormatContext *fmt_ctx, const AVPacket av_ts_make_string(duration, pkt->duration), av_ts_make_time_string(duration_time, pkt->duration, time_base), pkt->stream_index); +} +/* check that a given sample format is supported by the encoder */ +int FfmpegMuxStream::check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt) +{ + const enum AVSampleFormat *p = codec->sample_fmts; + + while (*p != AV_SAMPLE_FMT_NONE) { + if (*p == sample_fmt) + return 1; + p++; + } + return 0; +} +/* just pick the highest supported samplerate */ +int FfmpegMuxStream::select_sample_rate(const AVCodec *codec) +{ + const int *p; + int best_samplerate = 0; + + if (!codec->supported_samplerates) + return 44100; + + p = codec->supported_samplerates; + while (*p) { + if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate)) + best_samplerate = *p; + p++; + } + return best_samplerate; +} /* select layout with the highest channel count */ +int FfmpegMuxStream::select_channel_layout(const AVCodec *codec, AVChannelLayout *dst) +{ + const AVChannelLayout *p, *best_ch_layout = nullptr; + int best_nb_channels = 0; + AVChannelLayout channelLayout = AV_CHANNEL_LAYOUT_STEREO; + if (!codec->ch_layouts) + return av_channel_layout_copy(dst, &channelLayout); + + p = codec->ch_layouts; + while (p->nb_channels) { + int nb_channels = p->nb_channels; + + if (nb_channels > best_nb_channels) { + best_ch_layout = p; + best_nb_channels = nb_channels; + } + p++; + } + return av_channel_layout_copy(dst, best_ch_layout); +} +bool FfmpegMuxStream::ConvertAudioFrame(AVFrame *frame, AVCodecContext *c, OutputStream *ost) +{ + if (nullptr == frame) { + LogError("frame is null\n"); + return false; + } + int ret = 0; + int dst_nb_samples = 0; + /* convert samples from native format to destination codec format, using the resampler */ + /* compute destination number of samples */ + dst_nb_samples = av_rescale_rnd( + swr_get_delay(ost->swr_ctx, c->sample_rate) + frame->nb_samples, c->sample_rate, c->sample_rate, AV_ROUND_UP); + av_assert0(dst_nb_samples == frame->nb_samples); + + /* when we pass a frame to the encoder, it may keep a reference to it + * internally; + * make sure we do not overwrite it here + */ + ret = av_frame_make_writable(ost->frame); + if (ret < 0) { + LogError("av_frame_make_writable failed\n"); + return false; + } + + /* convert to destination format */ + ret = swr_convert(ost->swr_ctx, ost->frame->data, dst_nb_samples, (const uint8_t **)frame->data, frame->nb_samples); + if (ret < 0) { + LogError("Error while converting\n"); + return false; + } + frame = ost->frame; + + frame->pts = av_rescale_q(ost->samples_count, (AVRational){1, c->sample_rate}, c->time_base); + ost->samples_count += dst_nb_samples; + return true; } \ No newline at end of file diff --git a/utils/MediaBase/src/FfmpegMuxStream.h b/utils/MediaBase/src/FfmpegMuxStream.h index 2a76b7c..308cabc 100644 --- a/utils/MediaBase/src/FfmpegMuxStream.h +++ b/utils/MediaBase/src/FfmpegMuxStream.h @@ -26,6 +26,10 @@ public: // About combine file. StatusCode CloseOutputFile(void) override; void GetStreamData(const void *data, const size_t &size, const StreamInfo &streamInfo) override; +private: + void GetVideoStream(const void *data, const size_t &size, const StreamInfo &streamInfo); + void GetAudioStream(const void *data, const size_t &size, const StreamInfo &streamInfo); + private: static bool add_stream(OutputStream *ost, AVFormatContext *oc, const AVCodec **codec, enum AVCodecID codec_id); static void close_stream(AVFormatContext *oc, OutputStream *ost); @@ -34,14 +38,22 @@ private: static AVFrame *alloc_audio_frame(enum AVSampleFormat sample_fmt, const AVChannelLayout *channel_layout, int sample_rate, int nb_samples); static AVFrame *alloc_frame(enum AVPixelFormat pix_fmt, int width, int height); - static void InitCodec(AVCodec **codec, AVCodecContext **codec_ctx, AVFrame **frame); + static bool InitCodecVideo(enum AVCodecID codecId, AVCodec **codec, AVCodecContext **codec_ctx, AVFrame **frame); + static bool InitCodecAudio(enum AVCodecID codecId, AVCodec **codec, AVCodecContext **codec_ctx, AVFrame **frame); static int write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c, AVStream *st, AVFrame *frame, AVPacket *pkt); static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt); + static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt); + static int select_sample_rate(const AVCodec *codec); + static int select_channel_layout(const AVCodec *codec, AVChannelLayout *dst); + static bool ConvertAudioFrame(AVFrame *frame, AVCodecContext *c, OutputStream *ost); private: - AVCodec *mCodec; - AVCodecContext *mCodec_ctx; - AVFrame *mFrame; + AVCodec *mCodecVideo; + AVCodecContext *mCodecVideoContext; + AVFrame *mFrameVideo; + AVCodec *mCodecAudio; + AVCodecContext *mCodecAudioContext; + AVFrame *mFrameAudio; AVFormatContext *mOc; OutputStream mVideoSt; OutputStream mAudioSt;