From f60ab92134126ce70941a66a54ff41d2f8e89392 Mon Sep 17 00:00:00 2001 From: Fancy code <258828110.@qq.com> Date: Fri, 21 Jun 2024 15:27:11 +0800 Subject: [PATCH] Improve:MediaBase. --- utils/MediaBase/src/FfmpegBase.cpp | 62 +++ utils/MediaBase/src/FfmpegBase.h | 79 +++ utils/MediaBase/src/FfmpegMuxStream.cpp | 493 +++++++++++++++++++ utils/MediaBase/src/FfmpegMuxStream.h | 49 ++ utils/MediaBase/src/FfmpegReadFile.cpp | 144 ++++++ utils/MediaBase/src/FfmpegReadFile.h | 41 ++ utils/MediaBase/src/MediaBaseImpl.cpp | 626 +----------------------- utils/MediaBase/src/MediaBaseImpl.h | 94 +--- 8 files changed, 876 insertions(+), 712 deletions(-) create mode 100644 utils/MediaBase/src/FfmpegBase.cpp create mode 100644 utils/MediaBase/src/FfmpegBase.h create mode 100644 utils/MediaBase/src/FfmpegMuxStream.cpp create mode 100644 utils/MediaBase/src/FfmpegMuxStream.h create mode 100644 utils/MediaBase/src/FfmpegReadFile.cpp create mode 100644 utils/MediaBase/src/FfmpegReadFile.h diff --git a/utils/MediaBase/src/FfmpegBase.cpp b/utils/MediaBase/src/FfmpegBase.cpp new file mode 100644 index 0000000..f2781e0 --- /dev/null +++ b/utils/MediaBase/src/FfmpegBase.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 Fancy Code. + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "FfmpegBase.h" +#include "ILog.h" +#include "MediaBase.h" +#ifdef __cplusplus +extern "C" { +#endif +#include +#ifdef __cplusplus +} +#endif +FfmpegBase::FfmpegBase() : mType(MEDIA_HANDLE_TYPE_END), mTaskRuning(false) +{ +} +FfmpegBase::FfmpegBase(const MediaHandleType &type) : mType(type), mTaskRuning(false) +{ +} +void FfmpegBase::InitFfmpeg(void) +{ +} +void FfmpegBase::UnInitFfmpeg(void) +{ +} +const char *FfmpegBase::InputFormat(const MediaHandleType &type) +{ + switch (type) { + case MEDIA_HANDLE_TYPE_READ_H264: + LogInfo("InputFormat: h264.\n"); + return "h264"; + case MEDIA_HANDLE_TYPE_READ_G711A: + LogInfo("InputFormat: alaw.\n"); + return "alaw"; + default: + LogError("Unknown media type.\n"); + return nullptr; + } +} +AVMediaType FfmpegBase::MediaTypeConvert(const MediaHandleType &type) +{ + switch (type) { + case MediaHandleType::MEDIA_HANDLE_TYPE_READ_H264: + return AVMEDIA_TYPE_VIDEO; + case MediaHandleType::MEDIA_HANDLE_TYPE_READ_G711A: + return AVMEDIA_TYPE_AUDIO; + default: + LogWarning("Unknown media type.\n"); + return AVMEDIA_TYPE_UNKNOWN; + } +} \ No newline at end of file diff --git a/utils/MediaBase/src/FfmpegBase.h b/utils/MediaBase/src/FfmpegBase.h new file mode 100644 index 0000000..b8b37e6 --- /dev/null +++ b/utils/MediaBase/src/FfmpegBase.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 Fancy Code. + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef FFMPEG_BASE_H +#define FFMPEG_BASE_H +#include "IMediaBase.h" +#include "MediaBase.h" +#ifdef __cplusplus +extern "C" { +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __cplusplus +} +#endif +#include +// a wrapper around a single output AVStream +typedef struct OutputStream +{ + AVStream *st; + AVCodecContext *enc; + + /* pts of the next frame that will be generated */ + int64_t next_pts; + int samples_count; + + AVFrame *frame; + AVFrame *tmp_frame; + + AVPacket *tmp_pkt; + + float t, tincr, tincr2; + + struct SwsContext *sws_ctx; + struct SwrContext *swr_ctx; +} OutputStream; +class FfmpegBase : public IMediaBase, public std::enable_shared_from_this +{ +public: + FfmpegBase(); + FfmpegBase(const MediaHandleType &type); + virtual ~FfmpegBase() = default; + +protected: + void InitFfmpeg(void); + void UnInitFfmpeg(void); + +protected: + static const char *InputFormat(const MediaHandleType &type); + static AVMediaType MediaTypeConvert(const MediaHandleType &type); + +protected: + const MediaHandleType mType; + enum AVMediaType mFFmpegMediaType; + bool mTaskRuning; + std::thread mTaskTimerThread; +}; +#endif \ No newline at end of file diff --git a/utils/MediaBase/src/FfmpegMuxStream.cpp b/utils/MediaBase/src/FfmpegMuxStream.cpp new file mode 100644 index 0000000..ffe10e7 --- /dev/null +++ b/utils/MediaBase/src/FfmpegMuxStream.cpp @@ -0,0 +1,493 @@ +/* + * Copyright (c) 2023 Fancy Code. + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "FfmpegMuxStream.h" +#include "ILog.h" +#include "MediaBase.h" +#include "StatusCode.h" +#include +#include +#include +#include +#include +#ifdef __cplusplus +extern "C" { +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __cplusplus +} +#endif +#include +#include +#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) +{ + memset(&mVideoSt, 0, sizeof(mVideoSt)); + memset(&mAudioSt, 0, sizeof(mAudioSt)); +} +StatusCode FfmpegMuxStream::OpenOutputFile(const std::string &fileName) +{ + InitCodec(&mCodec, &mCodec_ctx, &mFrame); + int ret; + AVFormatContext *oc = nullptr; + int have_video = 0, have_audio = 0; + int encode_video = 0, encode_audio = 0; + const AVCodec *audio_codec, *video_codec; + AVDictionary *opt = nullptr; + avformat_alloc_output_context2(&oc, nullptr, "mp4", fileName.c_str()); + if (!oc) { + LogError("Could not deduce output format from file extension: using MPEG.\n"); + return CreateStatusCode(STATUS_CODE_NOT_OK); + } + mOc = oc; + const AVOutputFormat *fmt = oc->oformat; + /* Add the audio and video streams using the default format codecs + * and initialize the codecs. */ + if (fmt->video_codec != AV_CODEC_ID_NONE) { + add_stream(&mVideoSt, oc, &video_codec, fmt->video_codec); + have_video = 1; + encode_video = 1; + } + if (fmt->audio_codec != AV_CODEC_ID_NONE) { + add_stream(&mAudioSt, oc, &audio_codec, fmt->audio_codec); + have_audio = 1; + encode_audio = 1; + } /* Now that all the parameters are set, we can open the audio and + * video codecs and allocate the necessary encode buffers. */ + if (have_video) { + open_video(oc, video_codec, &mVideoSt, opt); + } + + if (have_audio) { + open_audio(oc, audio_codec, &mAudioSt, opt); + } + av_dump_format(oc, 0, fileName.c_str(), 1); + if (!(fmt->flags & AVFMT_NOFILE)) { + ret = avio_open(&oc->pb, fileName.c_str(), AVIO_FLAG_WRITE); + if (ret < 0) { + char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; + LogInfo("Could not open '%s': %s\n", + fileName.c_str(), + av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); + return CreateStatusCode(STATUS_CODE_NOT_OK); + } + } + /* Write the stream header, if any. */ + ret = avformat_write_header(oc, &opt); + if (ret < 0) { + char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; + LogInfo("Error occurred when opening output file: %s\n", + av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); + return CreateStatusCode(STATUS_CODE_NOT_OK); + } + return CreateStatusCode(STATUS_CODE_OK); +} +StatusCode FfmpegMuxStream::CloseOutputFile(void) +{ + av_write_trailer(mOc); + av_frame_free(&mFrame); + mFrame = nullptr; + avcodec_free_context(&mCodec_ctx); + + close_stream(mOc, &mVideoSt); + close_stream(mOc, &mAudioSt); + if (!(mOc->oformat->flags & AVFMT_NOFILE)) { + /* Close the output file. */ + avio_closep(&mOc->pb); + } + avformat_free_context(mOc); + return CreateStatusCode(STATUS_CODE_OK); +} +void FfmpegMuxStream::GetStreamData(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); + 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(mCodec_ctx, mFrame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } + if (ret < 0) { + LogInfo("Error during decoding\n"); + break; + } + write_frame(mOc, mVideoSt.enc, mVideoSt.st, mFrame, mVideoSt.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) +{ + AVCodecContext *c; + int i; + + /* find the encoder */ + *codec = avcodec_find_encoder(codec_id); + if (!(*codec)) { + LogError("Could not find encoder for '%s'\n", avcodec_get_name(codec_id)); + return false; + } + + ost->tmp_pkt = av_packet_alloc(); + if (!ost->tmp_pkt) { + LogError("Could not allocate AVPacket\n"); + return false; + } + + ost->st = avformat_new_stream(oc, nullptr); + if (!ost->st) { + LogError("Could not allocate stream\n"); + return false; + } + ost->st->id = oc->nb_streams - 1; + c = avcodec_alloc_context3(*codec); + if (!c) { + LogError("Could not alloc an encoding context\n"); + return false; + } + ost->enc = c; + + const AVChannelLayout src = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO; + switch ((*codec)->type) { + case AVMEDIA_TYPE_AUDIO: + c->sample_fmt = (*codec)->sample_fmts ? (*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; + c->bit_rate = 64000; + c->sample_rate = 44100; + if ((*codec)->supported_samplerates) { + c->sample_rate = (*codec)->supported_samplerates[0]; + for (i = 0; (*codec)->supported_samplerates[i]; i++) { + if ((*codec)->supported_samplerates[i] == 44100) + c->sample_rate = 44100; + } + } + av_channel_layout_copy(&c->ch_layout, &src); + ost->st->time_base = (AVRational){1, c->sample_rate}; + break; + + case AVMEDIA_TYPE_VIDEO: + c->codec_id = codec_id; + + c->bit_rate = 400000; + /* Resolution must be a multiple of two. */ + c->width = 352; + c->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. */ + ost->st->time_base = (AVRational){1, STREAM_FRAME_RATE}; + c->time_base = ost->st->time_base; + + c->gop_size = 12; /* emit one intra frame every twelve frames at most */ + c->pix_fmt = STREAM_PIX_FMT; + if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) { + /* just for testing, we also add B-frames */ + c->max_b_frames = 2; + } + if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) { + /* Needed to avoid using macroblocks in which some coeffs overflow. + * This does not happen with normal video, it just happens here as + * the motion of the chroma plane does not match the luma plane. */ + c->mb_decision = 2; + } + break; + + default: + break; + } + + /* Some formats want stream headers to be separate. */ + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { + c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + return true; +} +void FfmpegMuxStream::close_stream(AVFormatContext *oc, OutputStream *ost) +{ + avcodec_free_context(&ost->enc); + av_frame_free(&ost->frame); + av_frame_free(&ost->tmp_frame); + av_packet_free(&ost->tmp_pkt); + sws_freeContext(ost->sws_ctx); + swr_free(&ost->swr_ctx); +} +bool FfmpegMuxStream::open_video(AVFormatContext *oc, const AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg) +{ + + int ret; + AVCodecContext *c = ost->enc; + AVDictionary *opt = nullptr; + + av_dict_copy(&opt, opt_arg, 0); + + /* open the codec */ + ret = avcodec_open2(c, codec, &opt); + av_dict_free(&opt); + if (ret < 0) { + char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; + LogInfo("Could not open video codec: %s\n", av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); + return false; + } + + /* allocate and init a re-usable frame */ + ost->frame = alloc_frame(c->pix_fmt, c->width, c->height); + if (!ost->frame) { + LogInfo("Could not allocate video frame\n"); + return false; + } + + /* If the output format is not YUV420P, then a temporary YUV420P + * picture is needed too. It is then converted to the required + * output format. */ + ost->tmp_frame = nullptr; + if (c->pix_fmt != AV_PIX_FMT_YUV420P) { + ost->tmp_frame = alloc_frame(AV_PIX_FMT_YUV420P, c->width, c->height); + if (!ost->tmp_frame) { + LogInfo("Could not allocate temporary video frame\n"); + return false; + } + } + + /* copy the stream parameters to the muxer */ + ret = avcodec_parameters_from_context(ost->st->codecpar, c); + if (ret < 0) { + LogInfo("Could not copy the stream parameters\n"); + return false; + } + return true; +} +bool FfmpegMuxStream::open_audio(AVFormatContext *oc, const AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg) +{ + AVCodecContext *c; + int nb_samples; + int ret; + AVDictionary *opt = nullptr; + + c = ost->enc; + + /* open it */ + av_dict_copy(&opt, opt_arg, 0); + ret = avcodec_open2(c, codec, &opt); + av_dict_free(&opt); + if (ret < 0) { + char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; + LogInfo("Could not open audio codec: %s\n", av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); + return false; + } + + /* init signal generator */ + ost->t = 0; + ost->tincr = 2 * M_PI * 110.0 / c->sample_rate; + /* increment frequency by 110 Hz per second */ + ost->tincr2 = 2 * M_PI * 110.0 / c->sample_rate / c->sample_rate; + + if (c->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) + nb_samples = 10000; + else + nb_samples = c->frame_size; + + ost->frame = alloc_audio_frame(c->sample_fmt, &c->ch_layout, c->sample_rate, nb_samples); + ost->tmp_frame = alloc_audio_frame(AV_SAMPLE_FMT_S16, &c->ch_layout, c->sample_rate, nb_samples); + + /* copy the stream parameters to the muxer */ + ret = avcodec_parameters_from_context(ost->st->codecpar, c); + if (ret < 0) { + LogInfo("Could not copy the stream parameters\n"); + return false; + } + + /* create resampler context */ + ost->swr_ctx = swr_alloc(); + if (!ost->swr_ctx) { + LogInfo("Could not allocate resampler context\n"); + return false; + } + + /* set options */ + av_opt_set_chlayout(ost->swr_ctx, "in_chlayout", &c->ch_layout, 0); + av_opt_set_int(ost->swr_ctx, "in_sample_rate", c->sample_rate, 0); + av_opt_set_sample_fmt(ost->swr_ctx, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); + av_opt_set_chlayout(ost->swr_ctx, "out_chlayout", &c->ch_layout, 0); + av_opt_set_int(ost->swr_ctx, "out_sample_rate", c->sample_rate, 0); + av_opt_set_sample_fmt(ost->swr_ctx, "out_sample_fmt", c->sample_fmt, 0); + + /* initialize the resampling context */ + if ((ret = swr_init(ost->swr_ctx)) < 0) { + LogInfo("Failed to initialize the resampling context\n"); + return false; + } + return true; +} +AVFrame *FfmpegMuxStream::alloc_audio_frame(enum AVSampleFormat sample_fmt, const AVChannelLayout *channel_layout, + int sample_rate, int nb_samples) +{ + AVFrame *frame = av_frame_alloc(); + if (!frame) { + LogInfo("Error allocating an audio frame\n"); + return nullptr; + } + + frame->format = sample_fmt; + av_channel_layout_copy(&frame->ch_layout, channel_layout); + frame->sample_rate = sample_rate; + frame->nb_samples = nb_samples; + + if (nb_samples) { + if (av_frame_get_buffer(frame, 0) < 0) { + LogInfo("Error allocating an audio buffer\n"); + return nullptr; + } + } + + return frame; +} +AVFrame *FfmpegMuxStream::alloc_frame(enum AVPixelFormat pix_fmt, int width, int height) +{ + AVFrame *frame; + int ret; + + frame = av_frame_alloc(); + if (!frame) + return nullptr; + + frame->format = pix_fmt; + frame->width = width; + frame->height = height; + + /* allocate the buffers for the frame data */ + ret = av_frame_get_buffer(frame, 0); + if (ret < 0) { + LogInfo("Could not allocate frame data.\n"); + return nullptr; + } + + return frame; +} +void FfmpegMuxStream::InitCodec(AVCodec **codec, AVCodecContext **codec_ctx, AVFrame **frame) +{ + int ret = 0; + *codec = (AVCodec *)avcodec_find_decoder(AV_CODEC_ID_H264); + if (!(*codec)) { + LogError("Codec not found\n"); + return; + } + *codec_ctx = avcodec_alloc_context3((const AVCodec *)(*codec)); + if (!(*codec_ctx)) { + LogError("Could not allocate codec context\n"); + return; + } + 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; + } + *frame = av_frame_alloc(); + if (!frame) { + LogError("Could not allocate video frame\n"); + return; + } +} +int FfmpegMuxStream::write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c, AVStream *st, AVFrame *frame, + AVPacket *pkt) +{ + int ret; + + // send the frame to the encoder + ret = avcodec_send_frame(c, frame); + if (ret < 0) { + char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; + LogInfo("Error sending a frame to the encoder: %s\n", + av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); + return -1; + } + + while (ret >= 0) { + ret = avcodec_receive_packet(c, pkt); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } + if (ret < 0) { + char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; + LogInfo("Error encoding a frame: %s\n", av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); + return -1; + } + + /* rescale output packet timestamp values from codec to stream timebase */ + av_packet_rescale_ts(pkt, c->time_base, st->time_base); + pkt->stream_index = st->index; + + /* Write the compressed frame to the media file. */ + log_packet(fmt_ctx, pkt); + ret = av_interleaved_write_frame(fmt_ctx, pkt); + /* pkt is now blank (av_interleaved_write_frame() takes ownership of + * its contents and resets pkt), so that no unreferencing is necessary. + * This would be different if one used av_write_frame(). */ + if (ret < 0) { + char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; + LogInfo("Error while writing output packet: %s\n", + av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); + return -1; + } + } + + return ret == AVERROR_EOF ? 1 : 0; +} +void FfmpegMuxStream::log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt) +{ + char pts[AV_TS_MAX_STRING_SIZE] = {0}; + char dts[AV_TS_MAX_STRING_SIZE] = {0}; + char duration[AV_TS_MAX_STRING_SIZE] = {0}; + char pts_time[AV_TS_MAX_STRING_SIZE] = {0}; + char dts_time[AV_TS_MAX_STRING_SIZE] = {0}; + char duration_time[AV_TS_MAX_STRING_SIZE]; + AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base; + av_ts_make_string(pts, pkt->pts); + LogInfo("pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n", + av_ts_make_string(pts, pkt->pts), + av_ts_make_time_string(pts_time, pkt->pts, time_base), + av_ts_make_string(dts, pkt->dts), + av_ts_make_time_string(dts_time, pkt->dts, time_base), + av_ts_make_string(duration, pkt->duration), + av_ts_make_time_string(duration_time, pkt->duration, time_base), + pkt->stream_index); +} \ No newline at end of file diff --git a/utils/MediaBase/src/FfmpegMuxStream.h b/utils/MediaBase/src/FfmpegMuxStream.h new file mode 100644 index 0000000..2a76b7c --- /dev/null +++ b/utils/MediaBase/src/FfmpegMuxStream.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 Fancy Code. + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef FFMPEG_MUX_STREAM_H +#define FFMPEG_MUX_STREAM_H +#include "FfmpegBase.h" +class FfmpegMuxStream : virtual public FfmpegBase +{ +public: + FfmpegMuxStream(); + virtual ~FfmpegMuxStream() = default; + +public: // About combine file. + StatusCode OpenOutputFile(const std::string &fileName) override; + StatusCode CloseOutputFile(void) override; + void GetStreamData(const void *data, const size_t &size, const StreamInfo &streamInfo) override; + +private: + static bool add_stream(OutputStream *ost, AVFormatContext *oc, const AVCodec **codec, enum AVCodecID codec_id); + static void close_stream(AVFormatContext *oc, OutputStream *ost); + static bool open_video(AVFormatContext *oc, const AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg); + static bool open_audio(AVFormatContext *oc, const AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg); + 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 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); + +private: + AVCodec *mCodec; + AVCodecContext *mCodec_ctx; + AVFrame *mFrame; + AVFormatContext *mOc; + OutputStream mVideoSt; + OutputStream mAudioSt; +}; +#endif \ No newline at end of file diff --git a/utils/MediaBase/src/FfmpegReadFile.cpp b/utils/MediaBase/src/FfmpegReadFile.cpp new file mode 100644 index 0000000..aa750e9 --- /dev/null +++ b/utils/MediaBase/src/FfmpegReadFile.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2023 Fancy Code. + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "FfmpegReadFile.h" +#include "FfmpegBase.h" +#include "ILog.h" +#include "MediaBase.h" +#include "StatusCode.h" +#include +#include +#ifdef __cplusplus +extern "C" { +#endif +#include +#include +#include +#include +#ifdef __cplusplus +} +#endif +#include +#include +#include +#include +FfmpegReadFile::FfmpegReadFile() + : mReadVideoCallback(nullptr), mReadVideoCallbackContext(nullptr), mReadAudioCallback(nullptr), + mReadAudioCallbackContext(nullptr) +{ +} +StatusCode FfmpegReadFile::StartReadFile(const std::string &path) +{ + InitFfmpeg(); + int result = 0; + const AVInputFormat *iformat = av_find_input_format(FfmpegBase::InputFormat(mType)); + AVFormatContext *pFormatCtx = nullptr; + if ((result = avformat_open_input(&pFormatCtx, path.c_str(), iformat, nullptr)) < 0) { + char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; + LogError("Couldn't open file: %s, result=%s\n", + path.c_str(), + av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, result)); + return CreateStatusCode(STATUS_CODE_NOT_OK); + } + if (avformat_find_stream_info(pFormatCtx, nullptr) < 0) { + LogError("Couldn't find stream information.\n"); + avformat_close_input(&pFormatCtx); + return CreateStatusCode(STATUS_CODE_NOT_OK); + } + int mediaStreamIndex = -1; + for (unsigned int i = 0; i < pFormatCtx->nb_streams; i++) { + if (pFormatCtx->streams[i]->codecpar->codec_type == mFFmpegMediaType) { + mediaStreamIndex = i; + break; + } + } + if (mediaStreamIndex == -1) { + LogError("Didn't find a stream.\n"); + avformat_close_input(&pFormatCtx); + return CreateStatusCode(STATUS_CODE_NOT_OK); + } + auto taskTimerThread = [=](std::shared_ptr media) { + LogInfo("ReadFileThread start.\n"); + media->ReadFileThread(pFormatCtx, mediaStreamIndex); + }; + std::shared_ptr media = std::dynamic_pointer_cast(FfmpegBase::shared_from_this()); + mTaskTimerThread = std::thread(taskTimerThread, media); + return CreateStatusCode(STATUS_CODE_OK); +} +StatusCode FfmpegReadFile::StopReadFile(void) +{ + mTaskRuning = false; + if (mTaskTimerThread.joinable()) { + mTaskTimerThread.join(); + } + return CreateStatusCode(STATUS_CODE_OK); +} +StatusCode FfmpegReadFile::SetReadVideoCallback(ReadVideoFileCallback callback, void *context) +{ + mReadVideoCallback = callback; + mReadVideoCallbackContext = context; + return CreateStatusCode(STATUS_CODE_OK); +} +StatusCode FfmpegReadFile::SetReadAudioCallback(ReadVideoFileCallback callback, void *context) +{ + mReadAudioCallback = callback; + mReadAudioCallbackContext = context; + return CreateStatusCode(STATUS_CODE_OK); +} +void FfmpegReadFile::ReadFileThread(AVFormatContext *pFormatCtx, int mediaStreamIndex) +{ + mTaskRuning = true; + if (AVMEDIA_TYPE_VIDEO == mFFmpegMediaType && nullptr == mReadVideoCallback) { + LogWarning("ReadVideoCallback is null.\n"); + } + if (AVMEDIA_TYPE_AUDIO == mFFmpegMediaType && nullptr == mReadVideoCallback) { + LogWarning("ReadVideoCallback is null.\n"); + } + AVPacket packet; + unsigned int playTimeMs = 0; + // av_new_packet(&packet, AV_INPUT_BUFFER_MIN_SIZE); + while (av_read_frame(pFormatCtx, &packet) >= 0) { + if (nullptr == mReadVideoCallback) { + av_packet_unref(&packet); + continue; + } + if (false == mTaskRuning) { + LogInfo("Stop read file thread.\n"); + break; + } + // Checks whether the packet belongs to a video stream. + if (packet.stream_index == mediaStreamIndex) { + playTimeMs = (packet.duration * pFormatCtx->streams[mediaStreamIndex]->time_base.num * 1000) / + pFormatCtx->streams[mediaStreamIndex]->time_base.den; + // LogInfo("Frame data address: %p, length: %zu\n", packet.data, packet.size); + // LogInfo("Play time ms:%d\n", playTimeMs); + ReadFrame(&packet); + std::this_thread::sleep_for(std::chrono::milliseconds(playTimeMs)); + } + // Release the data packet. + av_packet_unref(&packet); + } + av_packet_unref(&packet); + + avformat_close_input(&pFormatCtx); +} +void inline FfmpegReadFile::ReadFrame(AVPacket *packet) +{ + if (AVMEDIA_TYPE_VIDEO == mFFmpegMediaType) { + mReadVideoCallback(packet->data, packet->size, mReadVideoCallbackContext); + } + else if (AVMEDIA_TYPE_AUDIO == mFFmpegMediaType) { + mReadVideoCallback(packet->data, packet->size, mReadVideoCallbackContext); + } +} \ No newline at end of file diff --git a/utils/MediaBase/src/FfmpegReadFile.h b/utils/MediaBase/src/FfmpegReadFile.h new file mode 100644 index 0000000..b3f3b93 --- /dev/null +++ b/utils/MediaBase/src/FfmpegReadFile.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 Fancy Code. + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef FFMPEG_READ_FILE_H +#define FFMPEG_READ_FILE_H +#include "FfmpegBase.h" +#include "MediaBase.h" +class FfmpegReadFile : virtual public FfmpegBase +{ +public: + FfmpegReadFile(); + virtual ~FfmpegReadFile() = default; + +public: // About read media file. + StatusCode StartReadFile(const std::string &path) override; + StatusCode StopReadFile(void) override; + StatusCode SetReadVideoCallback(ReadVideoFileCallback callback, void *context) override; + StatusCode SetReadAudioCallback(ReadVideoFileCallback callback, void *context) override; + +private: + void ReadFileThread(AVFormatContext *pFormatCtx, int video_stream_index); + void ReadFrame(AVPacket *packet); + +private: + ReadVideoFileCallback mReadVideoCallback; + void *mReadVideoCallbackContext; + ReadVideoFileCallback mReadAudioCallback; + void *mReadAudioCallbackContext; +}; +#endif \ No newline at end of file diff --git a/utils/MediaBase/src/MediaBaseImpl.cpp b/utils/MediaBase/src/MediaBaseImpl.cpp index ef63f8c..af63930 100644 --- a/utils/MediaBase/src/MediaBaseImpl.cpp +++ b/utils/MediaBase/src/MediaBaseImpl.cpp @@ -13,628 +13,10 @@ * limitations under the License. */ #include "MediaBaseImpl.h" -#include "ILog.h" +#include "FfmpegBase.h" +// #include "ILog.h" #include "MediaBase.h" -#include "StatusCode.h" -#include -#include -#include -#include -#include -#ifdef __cplusplus -extern "C" { -#endif -// #include -#include -#include -#include -#include -#include -#include -// #include -#include -#include -#include -#include -#include -// #include -// #include -#include -#include -#include -#include -#include -// #include -#ifdef __cplusplus -} -#endif -#include -#include -#include -#include -#include -#define STREAM_DURATION 10.0 -#define STREAM_FRAME_RATE 25 /* 25 images/s */ -#define STREAM_PIX_FMT AV_PIX_FMT_YUV420P /* default pix_fmt */ -MediaBaseImpl::MediaBaseImpl(const MediaHandleType &type) - : mCodec(nullptr), mCodec_ctx(nullptr), mFrame(nullptr), mOc(nullptr), mType(type), mReadVideoCallback(nullptr), - mReadVideoCallbackContext(nullptr), mTaskRuning(false) +MediaBaseImpl::MediaBaseImpl(const MediaHandleType &type) : FfmpegBase(type) { - MediaTypeConvert(); - memset(&mVideoSt, 0, sizeof(mVideoSt)); - memset(&mAudioSt, 0, sizeof(mAudioSt)); -} -StatusCode MediaBaseImpl::StartReadFile(const std::string &path) -{ - InitFfmpeg(); - int result = 0; - const AVInputFormat *iformat = av_find_input_format(InputFormat(mType)); - AVFormatContext *pFormatCtx = nullptr; - if ((result = avformat_open_input(&pFormatCtx, path.c_str(), iformat, nullptr)) < 0) { - char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; - LogError("Couldn't open file: %s, result=%s\n", - path.c_str(), - av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, result)); - return CreateStatusCode(STATUS_CODE_NOT_OK); - } - if (avformat_find_stream_info(pFormatCtx, nullptr) < 0) { - LogError("Couldn't find stream information.\n"); - avformat_close_input(&pFormatCtx); - return CreateStatusCode(STATUS_CODE_NOT_OK); - } - int mediaStreamIndex = -1; - for (unsigned int i = 0; i < pFormatCtx->nb_streams; i++) { - if (pFormatCtx->streams[i]->codecpar->codec_type == mFFmpegMediaType) { - mediaStreamIndex = i; - break; - } - } - if (mediaStreamIndex == -1) { - LogError("Didn't find a stream.\n"); - avformat_close_input(&pFormatCtx); - return CreateStatusCode(STATUS_CODE_NOT_OK); - } - auto taskTimerThread = [=](std::shared_ptr media) { - LogInfo("ReadFileThread start.\n"); - media->ReadFileThread(pFormatCtx, mediaStreamIndex); - }; - std::shared_ptr media = shared_from_this(); - mTaskTimerThread = std::thread(taskTimerThread, media); - return CreateStatusCode(STATUS_CODE_OK); -} -StatusCode MediaBaseImpl::StopReadFile(void) -{ - mTaskRuning = false; - if (mTaskTimerThread.joinable()) { - mTaskTimerThread.join(); - } - return CreateStatusCode(STATUS_CODE_OK); -} -StatusCode MediaBaseImpl::SetReadVideoCallback(ReadVideoFileCallback callback, void *context) -{ - mReadVideoCallback = callback; - mReadVideoCallbackContext = context; - return CreateStatusCode(STATUS_CODE_OK); -} -StatusCode MediaBaseImpl::SetReadAudioCallback(ReadVideoFileCallback callback, void *context) -{ - mReadAudioCallback = callback; - mReadAudioCallbackContext = context; - return CreateStatusCode(STATUS_CODE_OK); -} -StatusCode MediaBaseImpl::OpenOutputFile(const std::string &fileName) -{ - InitCodec(&mCodec, &mCodec_ctx, &mFrame); - int ret; - AVFormatContext *oc = nullptr; - int have_video = 0, have_audio = 0; - int encode_video = 0, encode_audio = 0; - const AVCodec *audio_codec, *video_codec; - AVDictionary *opt = nullptr; - avformat_alloc_output_context2(&oc, nullptr, "mp4", fileName.c_str()); - if (!oc) { - LogError("Could not deduce output format from file extension: using MPEG.\n"); - return CreateStatusCode(STATUS_CODE_NOT_OK); - } - mOc = oc; - const AVOutputFormat *fmt = oc->oformat; - /* Add the audio and video streams using the default format codecs - * and initialize the codecs. */ - if (fmt->video_codec != AV_CODEC_ID_NONE) { - add_stream(&mVideoSt, oc, &video_codec, fmt->video_codec); - have_video = 1; - encode_video = 1; - } - if (fmt->audio_codec != AV_CODEC_ID_NONE) { - add_stream(&mAudioSt, oc, &audio_codec, fmt->audio_codec); - have_audio = 1; - encode_audio = 1; - } /* Now that all the parameters are set, we can open the audio and - * video codecs and allocate the necessary encode buffers. */ - if (have_video) { - open_video(oc, video_codec, &mVideoSt, opt); - } - - if (have_audio) { - open_audio(oc, audio_codec, &mAudioSt, opt); - } - av_dump_format(oc, 0, fileName.c_str(), 1); - if (!(fmt->flags & AVFMT_NOFILE)) { - ret = avio_open(&oc->pb, fileName.c_str(), AVIO_FLAG_WRITE); - if (ret < 0) { - char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; - LogInfo("Could not open '%s': %s\n", - fileName.c_str(), - av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); - return CreateStatusCode(STATUS_CODE_NOT_OK); - } - } - /* Write the stream header, if any. */ - ret = avformat_write_header(oc, &opt); - if (ret < 0) { - char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; - LogInfo("Error occurred when opening output file: %s\n", - av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); - return CreateStatusCode(STATUS_CODE_NOT_OK); - } - return CreateStatusCode(STATUS_CODE_OK); -} -StatusCode MediaBaseImpl::CloseOutputFile(void) -{ - av_write_trailer(mOc); - av_frame_free(&mFrame); - mFrame = nullptr; - avcodec_free_context(&mCodec_ctx); - - close_stream(mOc, &mVideoSt); - close_stream(mOc, &mAudioSt); - if (!(mOc->oformat->flags & AVFMT_NOFILE)) { - /* Close the output file. */ - avio_closep(&mOc->pb); - } - avformat_free_context(mOc); - return CreateStatusCode(STATUS_CODE_OK); -} -void MediaBaseImpl::GetStreamData(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); - 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(mCodec_ctx, mFrame); - if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { - break; - } - if (ret < 0) { - LogInfo("Error during decoding\n"); - break; - } - write_frame(mOc, mVideoSt.enc, mVideoSt.st, mFrame, mVideoSt.tmp_pkt); - break; - } - // av_packet_unref(packet); - av_packet_free(&packet); -} -void MediaBaseImpl::InitFfmpeg(void) -{ -} -void MediaBaseImpl::UnInitFfmpeg(void) -{ -} -void MediaBaseImpl::ReadFileThread(AVFormatContext *pFormatCtx, int mediaStreamIndex) -{ - mTaskRuning = true; - if (AVMEDIA_TYPE_VIDEO == mFFmpegMediaType && nullptr == mReadVideoCallback) { - LogWarning("ReadVideoCallback is null.\n"); - } - if (AVMEDIA_TYPE_AUDIO == mFFmpegMediaType && nullptr == mReadVideoCallback) { - LogWarning("ReadVideoCallback is null.\n"); - } - AVPacket packet; - unsigned int playTimeMs = 0; - // av_new_packet(&packet, AV_INPUT_BUFFER_MIN_SIZE); - while (av_read_frame(pFormatCtx, &packet) >= 0) { - if (nullptr == mReadVideoCallback) { - av_packet_unref(&packet); - continue; - } - if (false == mTaskRuning) { - LogInfo("Stop read file thread.\n"); - break; - } - // Checks whether the packet belongs to a video stream. - if (packet.stream_index == mediaStreamIndex) { - playTimeMs = (packet.duration * pFormatCtx->streams[mediaStreamIndex]->time_base.num * 1000) / - pFormatCtx->streams[mediaStreamIndex]->time_base.den; - // LogInfo("Frame data address: %p, length: %zu\n", packet.data, packet.size); - // LogInfo("Play time ms:%d\n", playTimeMs); - ReadFrame(&packet); - std::this_thread::sleep_for(std::chrono::milliseconds(playTimeMs)); - } - // Release the data packet. - av_packet_unref(&packet); - } - av_packet_unref(&packet); - - avformat_close_input(&pFormatCtx); -} -void inline MediaBaseImpl::ReadFrame(AVPacket *packet) -{ - if (AVMEDIA_TYPE_VIDEO == mFFmpegMediaType) { - mReadVideoCallback(packet->data, packet->size, mReadVideoCallbackContext); - } - else if (AVMEDIA_TYPE_AUDIO == mFFmpegMediaType) { - mReadVideoCallback(packet->data, packet->size, mReadVideoCallbackContext); - } -} -void MediaBaseImpl::MediaTypeConvert(void) -{ - switch (mType) { - case MediaHandleType::MEDIA_HANDLE_TYPE_READ_H264: - mFFmpegMediaType = AVMEDIA_TYPE_VIDEO; - break; - case MediaHandleType::MEDIA_HANDLE_TYPE_READ_G711A: - mFFmpegMediaType = AVMEDIA_TYPE_AUDIO; - break; - default: - LogWarning("Unknown media type.\n"); - break; - } -} -const char *MediaBaseImpl::InputFormat(const MediaHandleType &type) -{ - switch (type) { - case MEDIA_HANDLE_TYPE_READ_H264: - LogInfo("InputFormat: h264.\n"); - return "h264"; - case MEDIA_HANDLE_TYPE_READ_G711A: - LogInfo("InputFormat: alaw.\n"); - return "alaw"; - default: - LogError("Unknown media type.\n"); - return nullptr; - } -} -bool MediaBaseImpl::add_stream(OutputStream *ost, AVFormatContext *oc, const AVCodec **codec, enum AVCodecID codec_id) -{ - AVCodecContext *c; - int i; - - /* find the encoder */ - *codec = avcodec_find_encoder(codec_id); - if (!(*codec)) { - LogError("Could not find encoder for '%s'\n", avcodec_get_name(codec_id)); - return false; - } - - ost->tmp_pkt = av_packet_alloc(); - if (!ost->tmp_pkt) { - LogError("Could not allocate AVPacket\n"); - return false; - } - - ost->st = avformat_new_stream(oc, nullptr); - if (!ost->st) { - LogError("Could not allocate stream\n"); - return false; - } - ost->st->id = oc->nb_streams - 1; - c = avcodec_alloc_context3(*codec); - if (!c) { - LogError("Could not alloc an encoding context\n"); - return false; - } - ost->enc = c; - - const AVChannelLayout src = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO; - switch ((*codec)->type) { - case AVMEDIA_TYPE_AUDIO: - c->sample_fmt = (*codec)->sample_fmts ? (*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; - c->bit_rate = 64000; - c->sample_rate = 44100; - if ((*codec)->supported_samplerates) { - c->sample_rate = (*codec)->supported_samplerates[0]; - for (i = 0; (*codec)->supported_samplerates[i]; i++) { - if ((*codec)->supported_samplerates[i] == 44100) - c->sample_rate = 44100; - } - } - av_channel_layout_copy(&c->ch_layout, &src); - ost->st->time_base = (AVRational){1, c->sample_rate}; - break; - - case AVMEDIA_TYPE_VIDEO: - c->codec_id = codec_id; - - c->bit_rate = 400000; - /* Resolution must be a multiple of two. */ - c->width = 352; - c->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. */ - ost->st->time_base = (AVRational){1, STREAM_FRAME_RATE}; - c->time_base = ost->st->time_base; - - c->gop_size = 12; /* emit one intra frame every twelve frames at most */ - c->pix_fmt = STREAM_PIX_FMT; - if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) { - /* just for testing, we also add B-frames */ - c->max_b_frames = 2; - } - if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) { - /* Needed to avoid using macroblocks in which some coeffs overflow. - * This does not happen with normal video, it just happens here as - * the motion of the chroma plane does not match the luma plane. */ - c->mb_decision = 2; - } - break; - - default: - break; - } - - /* Some formats want stream headers to be separate. */ - if (oc->oformat->flags & AVFMT_GLOBALHEADER) { - c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - } - return true; -} -void MediaBaseImpl::close_stream(AVFormatContext *oc, OutputStream *ost) -{ - avcodec_free_context(&ost->enc); - av_frame_free(&ost->frame); - av_frame_free(&ost->tmp_frame); - av_packet_free(&ost->tmp_pkt); - sws_freeContext(ost->sws_ctx); - swr_free(&ost->swr_ctx); -} -bool MediaBaseImpl::open_video(AVFormatContext *oc, const AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg) -{ - - int ret; - AVCodecContext *c = ost->enc; - AVDictionary *opt = nullptr; - - av_dict_copy(&opt, opt_arg, 0); - - /* open the codec */ - ret = avcodec_open2(c, codec, &opt); - av_dict_free(&opt); - if (ret < 0) { - char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; - LogInfo("Could not open video codec: %s\n", av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); - return false; - } - - /* allocate and init a re-usable frame */ - ost->frame = alloc_frame(c->pix_fmt, c->width, c->height); - if (!ost->frame) { - LogInfo("Could not allocate video frame\n"); - return false; - } - - /* If the output format is not YUV420P, then a temporary YUV420P - * picture is needed too. It is then converted to the required - * output format. */ - ost->tmp_frame = nullptr; - if (c->pix_fmt != AV_PIX_FMT_YUV420P) { - ost->tmp_frame = alloc_frame(AV_PIX_FMT_YUV420P, c->width, c->height); - if (!ost->tmp_frame) { - LogInfo("Could not allocate temporary video frame\n"); - return false; - } - } - - /* copy the stream parameters to the muxer */ - ret = avcodec_parameters_from_context(ost->st->codecpar, c); - if (ret < 0) { - LogInfo("Could not copy the stream parameters\n"); - return false; - } - return true; -} -bool MediaBaseImpl::open_audio(AVFormatContext *oc, const AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg) -{ - AVCodecContext *c; - int nb_samples; - int ret; - AVDictionary *opt = nullptr; - - c = ost->enc; - - /* open it */ - av_dict_copy(&opt, opt_arg, 0); - ret = avcodec_open2(c, codec, &opt); - av_dict_free(&opt); - if (ret < 0) { - char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; - LogInfo("Could not open audio codec: %s\n", av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); - return false; - } - - /* init signal generator */ - ost->t = 0; - ost->tincr = 2 * M_PI * 110.0 / c->sample_rate; - /* increment frequency by 110 Hz per second */ - ost->tincr2 = 2 * M_PI * 110.0 / c->sample_rate / c->sample_rate; - - if (c->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) - nb_samples = 10000; - else - nb_samples = c->frame_size; - - ost->frame = alloc_audio_frame(c->sample_fmt, &c->ch_layout, c->sample_rate, nb_samples); - ost->tmp_frame = alloc_audio_frame(AV_SAMPLE_FMT_S16, &c->ch_layout, c->sample_rate, nb_samples); - - /* copy the stream parameters to the muxer */ - ret = avcodec_parameters_from_context(ost->st->codecpar, c); - if (ret < 0) { - LogInfo("Could not copy the stream parameters\n"); - return false; - } - - /* create resampler context */ - ost->swr_ctx = swr_alloc(); - if (!ost->swr_ctx) { - LogInfo("Could not allocate resampler context\n"); - return false; - } - - /* set options */ - av_opt_set_chlayout(ost->swr_ctx, "in_chlayout", &c->ch_layout, 0); - av_opt_set_int(ost->swr_ctx, "in_sample_rate", c->sample_rate, 0); - av_opt_set_sample_fmt(ost->swr_ctx, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); - av_opt_set_chlayout(ost->swr_ctx, "out_chlayout", &c->ch_layout, 0); - av_opt_set_int(ost->swr_ctx, "out_sample_rate", c->sample_rate, 0); - av_opt_set_sample_fmt(ost->swr_ctx, "out_sample_fmt", c->sample_fmt, 0); - - /* initialize the resampling context */ - if ((ret = swr_init(ost->swr_ctx)) < 0) { - LogInfo("Failed to initialize the resampling context\n"); - return false; - } - return true; -} -AVFrame *MediaBaseImpl::alloc_audio_frame(enum AVSampleFormat sample_fmt, const AVChannelLayout *channel_layout, - int sample_rate, int nb_samples) -{ - AVFrame *frame = av_frame_alloc(); - if (!frame) { - LogInfo("Error allocating an audio frame\n"); - return nullptr; - } - - frame->format = sample_fmt; - av_channel_layout_copy(&frame->ch_layout, channel_layout); - frame->sample_rate = sample_rate; - frame->nb_samples = nb_samples; - - if (nb_samples) { - if (av_frame_get_buffer(frame, 0) < 0) { - LogInfo("Error allocating an audio buffer\n"); - return nullptr; - } - } - - return frame; -} -AVFrame *MediaBaseImpl::alloc_frame(enum AVPixelFormat pix_fmt, int width, int height) -{ - AVFrame *frame; - int ret; - - frame = av_frame_alloc(); - if (!frame) - return nullptr; - - frame->format = pix_fmt; - frame->width = width; - frame->height = height; - - /* allocate the buffers for the frame data */ - ret = av_frame_get_buffer(frame, 0); - if (ret < 0) { - LogInfo("Could not allocate frame data.\n"); - return nullptr; - } - - return frame; -} -void MediaBaseImpl::InitCodec(AVCodec **codec, AVCodecContext **codec_ctx, AVFrame **frame) -{ - int ret = 0; - *codec = (AVCodec *)avcodec_find_decoder(AV_CODEC_ID_H264); - if (!(*codec)) { - LogError("Codec not found\n"); - return; - } - *codec_ctx = avcodec_alloc_context3((const AVCodec *)(*codec)); - if (!(*codec_ctx)) { - LogError("Could not allocate codec context\n"); - return; - } - 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; - } - *frame = av_frame_alloc(); - if (!frame) { - LogError("Could not allocate video frame\n"); - return; - } -} -int MediaBaseImpl::write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c, AVStream *st, AVFrame *frame, AVPacket *pkt) -{ - int ret; - - // send the frame to the encoder - ret = avcodec_send_frame(c, frame); - if (ret < 0) { - char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; - LogInfo("Error sending a frame to the encoder: %s\n", - av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); - return -1; - } - - while (ret >= 0) { - ret = avcodec_receive_packet(c, pkt); - if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { - break; - } - if (ret < 0) { - char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; - LogInfo("Error encoding a frame: %s\n", av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); - return -1; - } - - /* rescale output packet timestamp values from codec to stream timebase */ - av_packet_rescale_ts(pkt, c->time_base, st->time_base); - pkt->stream_index = st->index; - - /* Write the compressed frame to the media file. */ - log_packet(fmt_ctx, pkt); - ret = av_interleaved_write_frame(fmt_ctx, pkt); - /* pkt is now blank (av_interleaved_write_frame() takes ownership of - * its contents and resets pkt), so that no unreferencing is necessary. - * This would be different if one used av_write_frame(). */ - if (ret < 0) { - char error_str[AV_ERROR_MAX_STRING_SIZE] = {0}; - LogInfo("Error while writing output packet: %s\n", - av_make_error_string(error_str, AV_ERROR_MAX_STRING_SIZE, ret)); - return -1; - } - } - - return ret == AVERROR_EOF ? 1 : 0; -} -void MediaBaseImpl::log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt) -{ - char pts[AV_TS_MAX_STRING_SIZE] = {0}; - char dts[AV_TS_MAX_STRING_SIZE] = {0}; - char duration[AV_TS_MAX_STRING_SIZE] = {0}; - char pts_time[AV_TS_MAX_STRING_SIZE] = {0}; - char dts_time[AV_TS_MAX_STRING_SIZE] = {0}; - char duration_time[AV_TS_MAX_STRING_SIZE]; - AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base; - av_ts_make_string(pts, pkt->pts); - printf("pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n", - av_ts_make_string(pts, pkt->pts), - av_ts_make_time_string(pts_time, pkt->pts, time_base), - av_ts_make_string(dts, pkt->dts), - av_ts_make_time_string(dts_time, pkt->dts, time_base), - av_ts_make_string(duration, pkt->duration), - av_ts_make_time_string(duration_time, pkt->duration, time_base), - pkt->stream_index); + mFFmpegMediaType = FfmpegBase::MediaTypeConvert(mType); } \ No newline at end of file diff --git a/utils/MediaBase/src/MediaBaseImpl.h b/utils/MediaBase/src/MediaBaseImpl.h index 9177fed..060c9e2 100644 --- a/utils/MediaBase/src/MediaBaseImpl.h +++ b/utils/MediaBase/src/MediaBaseImpl.h @@ -14,101 +14,15 @@ */ #ifndef MEDIA_BASE_IMPL_H #define MEDIA_BASE_IMPL_H +#include "FfmpegBase.h" +#include "FfmpegMuxStream.h" +#include "FfmpegReadFile.h" #include "IMediaBase.h" -#ifdef __cplusplus -extern "C" { -#endif -// #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -// #include -#ifdef __cplusplus -} -#endif #include -// a wrapper around a single output AVStream -typedef struct OutputStream -{ - AVStream *st; - AVCodecContext *enc; - - /* pts of the next frame that will be generated */ - int64_t next_pts; - int samples_count; - - AVFrame *frame; - AVFrame *tmp_frame; - - AVPacket *tmp_pkt; - - float t, tincr, tincr2; - - struct SwsContext *sws_ctx; - struct SwrContext *swr_ctx; -} OutputStream; -class MediaBaseImpl : public IMediaBase, public std::enable_shared_from_this +class MediaBaseImpl : public FfmpegReadFile, public FfmpegMuxStream { public: MediaBaseImpl(const MediaHandleType &type); virtual ~MediaBaseImpl() = default; - -public: // About read media file. - StatusCode StartReadFile(const std::string &path) override; - StatusCode StopReadFile(void) override; - StatusCode SetReadVideoCallback(ReadVideoFileCallback callback, void *context) override; - StatusCode SetReadAudioCallback(ReadVideoFileCallback callback, void *context) override; - -public: // About combine file. - StatusCode OpenOutputFile(const std::string &fileName) override; - StatusCode CloseOutputFile(void) override; - void GetStreamData(const void *data, const size_t &size, const StreamInfo &streamInfo) override; - -private: - void InitFfmpeg(void); - void UnInitFfmpeg(void); - void ReadFileThread(AVFormatContext *pFormatCtx, int video_stream_index); - void ReadFrame(AVPacket *packet); - void MediaTypeConvert(void); - static const char *InputFormat(const MediaHandleType &type); - -private: - static bool add_stream(OutputStream *ost, AVFormatContext *oc, const AVCodec **codec, enum AVCodecID codec_id); - static void close_stream(AVFormatContext *oc, OutputStream *ost); - static bool open_video(AVFormatContext *oc, const AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg); - static bool open_audio(AVFormatContext *oc, const AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg); - 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 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); - -private: - AVCodec *mCodec; - AVCodecContext *mCodec_ctx; - AVFrame *mFrame; - AVFormatContext *mOc; - OutputStream mVideoSt; - OutputStream mAudioSt; - -private: - const MediaHandleType mType; - enum AVMediaType mFFmpegMediaType; - ReadVideoFileCallback mReadVideoCallback; - void *mReadVideoCallbackContext; - ReadVideoFileCallback mReadAudioCallback; - void *mReadAudioCallbackContext; - bool mTaskRuning; - std::thread mTaskTimerThread; }; #endif \ No newline at end of file