| /** |
| * Copyright (C) 2020-2021 Savoir-faire Linux Inc. |
| * |
| * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #include "FilterAudioSubscriber.h" |
| |
| extern "C" { |
| #include <libavcodec/avcodec.h> |
| #include <libavformat/avformat.h> |
| #include <libavfilter/buffersrc.h> |
| } |
| #include <frameUtils.h> |
| |
| #include <pluglog.h> |
| |
| const std::string TAG = "Filter"; |
| const char sep = separator(); |
| |
| namespace jami { |
| |
| FilterAudioSubscriber::FilterAudioSubscriber(const std::string& dataPath, const std::string& irFile) |
| : path_ {dataPath} |
| { |
| setIRFile(irFile); |
| } |
| |
| FilterAudioSubscriber::~FilterAudioSubscriber() |
| { |
| std::ostringstream oss; |
| oss << "~FilterMediaProcessor" << std::endl; |
| Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); |
| } |
| |
| void |
| FilterAudioSubscriber::setIRFile(const std::string& irFile) |
| { |
| irFile_ = path_ + "/" + irFile; |
| firstRun = true; |
| reverbFilter_.clean(); |
| } |
| |
| void |
| FilterAudioSubscriber::setFilterDescription(const int pSampleRate, const int pSamples) |
| { |
| int rSamples = 1024; // due to afir internal fifo |
| int midSampleRate = pSampleRate * rSamples / pSamples; |
| filterDescription_ |
| = "[ input ] aformat=sample_fmts=s16:sample_rates=" + std::to_string(midSampleRate) |
| + ":channel_layouts=stereo [ resample1 ] , " |
| "[ resample1 ] [ ir0 ] afir=maxir=1:wet=10:dry=10:irgain=1:irfmt=mono:maxp=" |
| + std::to_string(rSamples) + ":minp=" + std::to_string(rSamples) |
| + " [ reverb ] , " |
| "[ reverb ] aformat=sample_fmts=s16:sample_rates=" |
| + std::to_string(pSampleRate) + ":channel_layouts=stereo "; |
| } |
| |
| AudioFormat |
| FilterAudioSubscriber::getIRAVFrameInfos() |
| { |
| AudioFormat rAudioFormat = AudioFormat(0, 0); |
| int i; |
| |
| pFormatCtx_ = avformat_alloc_context(); |
| // Open |
| if (avformat_open_input(&pFormatCtx_, irFile_.c_str(), NULL, NULL) != 0) { |
| Plog::log(Plog::LogPriority::INFO, TAG, "Couldn't open input stream."); |
| return rAudioFormat; |
| } |
| // Retrieve stream information |
| if (avformat_find_stream_info(pFormatCtx_, NULL) < 0) { |
| Plog::log(Plog::LogPriority::INFO, TAG, "Couldn't find stream information."); |
| return rAudioFormat; |
| } |
| |
| // Dump valid information onto standard error |
| av_dump_format(pFormatCtx_, 0, irFile_.c_str(), false); |
| |
| // Find the first audio stream |
| audioStream_ = -1; |
| for (i = 0; i < static_cast<int>(pFormatCtx_->nb_streams); i++) |
| if (pFormatCtx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { |
| audioStream_ = i; |
| break; |
| } |
| |
| if (audioStream_ == -1) { |
| Plog::log(Plog::LogPriority::INFO, TAG, "Didn't find a audio stream."); |
| return rAudioFormat; |
| } |
| rAudioFormat = AudioFormat(pFormatCtx_->streams[audioStream_]->codecpar->sample_rate, |
| pFormatCtx_->streams[audioStream_]->codecpar->channels, |
| static_cast<AVSampleFormat>( |
| pFormatCtx_->streams[audioStream_]->codecpar->format)); |
| |
| return rAudioFormat; |
| } |
| |
| void |
| FilterAudioSubscriber::setIRAVFrame() |
| { |
| int ret, got_frame; |
| AVCodecContext* pCodecCtx; |
| AVPacket* packet; |
| |
| FILE* pFile = fopen(irFile_.c_str(), "rb"); |
| |
| const AVCodec* pCodec = avcodec_find_decoder(pFormatCtx_->streams[audioStream_]->codecpar->codec_id); |
| if (pCodec == NULL) { |
| Plog::log(Plog::LogPriority::INFO, TAG, "Codec not found."); |
| return; |
| } |
| |
| pCodecCtx = avcodec_alloc_context3(pCodec); |
| // Open codec |
| if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { |
| Plog::log(Plog::LogPriority::INFO, TAG, "Could not open codec."); |
| return; |
| } |
| |
| packet = av_packet_alloc(); |
| int buffsize |
| = av_samples_get_buffer_size(NULL, |
| pFormatCtx_->streams[audioStream_]->codecpar->channels, |
| pFormatCtx_->streams[audioStream_]->codecpar->frame_size, |
| static_cast<AVSampleFormat>( |
| pFormatCtx_->streams[audioStream_]->codecpar->format), |
| 1); |
| int frames = static_cast<int>(pFormatCtx_->streams[audioStream_]->codecpar->sample_rate |
| / pFormatCtx_->streams[audioStream_]->codecpar->frame_size); |
| int AUDIO_INBUF_SIZE = buffsize * frames / 4; |
| uint8_t* inbuf = new uint8_t[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE]; |
| |
| packet->data = inbuf; |
| packet->size = static_cast<int>(fread(inbuf, 1, AUDIO_INBUF_SIZE, pFile)); |
| int idx = 0; |
| AVFrame* pFrame = av_frame_alloc(); |
| |
| if (avcodec_send_packet(pCodecCtx, packet) < 0) { |
| avcodec_close(pCodecCtx); |
| av_frame_unref(pFrame); |
| av_frame_free(&pFrame); |
| av_packet_free(&packet); |
| avformat_close_input(&pFormatCtx_); |
| avformat_free_context(pFormatCtx_); |
| Plog::log(Plog::LogPriority::INFO, TAG, "Error submitting the packet to the decoder"); |
| return; |
| } |
| |
| while (packet->size > 0 && idx < frames) { |
| idx++; |
| got_frame = 0; |
| |
| if (avcodec_receive_frame(pCodecCtx, pFrame) < 0) { |
| break; |
| } |
| |
| auto len = av_get_bytes_per_sample(pCodecCtx->sample_fmt); |
| |
| reverbFilter_.feedInput(pFrame, "ir0"); |
| packet->size -= len; |
| packet->data += len; |
| } |
| av_frame_unref(pFrame); |
| |
| reverbFilter_.feedEOF("ir0"); |
| |
| fclose(pFile); |
| avcodec_close(pCodecCtx); |
| av_frame_free(&pFrame); |
| av_packet_free(&packet); |
| avformat_close_input(&pFormatCtx_); |
| avformat_free_context(pFormatCtx_); |
| } |
| |
| void |
| FilterAudioSubscriber::update(Observable<AVFrame*>*, AVFrame* const& pluginFrame) |
| { |
| if (!pluginFrame) |
| return; |
| |
| if (firstRun) { |
| setFilterDescription(pluginFrame->sample_rate, pluginFrame->nb_samples); |
| AudioFormat afmt_ = AudioFormat(pluginFrame->sample_rate, |
| pluginFrame->channels, |
| static_cast<AVSampleFormat>(pluginFrame->format)); |
| AudioFormat irfmt_ = getIRAVFrameInfos(); |
| MediaStream ms_ = MediaStream("input", afmt_); |
| MediaStream irms_ = MediaStream("ir0", irfmt_); |
| reverbFilter_.initialize(filterDescription_, {ms_, irms_}); |
| setIRAVFrame(); |
| firstRun = false; |
| } |
| |
| if (!reverbFilter_.initialized_) |
| return; |
| |
| AVFrame* filteredFrame; |
| if (reverbFilter_.feedInput(pluginFrame, "input") == 0) { |
| if ((filteredFrame = reverbFilter_.readOutput())) |
| moveFrom(pluginFrame, filteredFrame); |
| av_frame_unref(filteredFrame); |
| av_frame_free(&filteredFrame); |
| } |
| } |
| |
| void |
| FilterAudioSubscriber::attached(Observable<AVFrame*>* observable) |
| { |
| std::ostringstream oss; |
| oss << "::Attached ! " << std::endl; |
| Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); |
| observable_ = observable; |
| } |
| |
| void |
| FilterAudioSubscriber::detached(Observable<AVFrame*>*) |
| { |
| reverbFilter_.clean(); |
| firstRun = true; |
| observable_ = nullptr; |
| std::ostringstream oss; |
| oss << "::Detached()" << std::endl; |
| Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); |
| } |
| |
| void |
| FilterAudioSubscriber::detach() |
| { |
| if (observable_) { |
| reverbFilter_.clean(); |
| firstRun = true; |
| std::ostringstream oss; |
| oss << "::Calling detach()" << std::endl; |
| Plog::log(Plog::LogPriority::INFO, TAG, oss.str()); |
| observable_->detach(this); |
| } |
| } |
| } // namespace jami |