Use configured resolution for login/outgame/ingame
[ryzomcore.git] / nel / src / sound / audio_decoder_ffmpeg.cpp
bloba0389e2e731742e8468c300f92e188efb80c0ccb
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2018-2019 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include "stdsound.h"
20 #include <nel/sound/audio_decoder_ffmpeg.h>
22 #define __STDC_CONSTANT_MACROS
23 extern "C"
25 #include <libavcodec/avcodec.h>
26 #include <libavformat/avformat.h>
27 #include <libswresample/swresample.h>
28 #include <libavutil/opt.h>
31 using namespace std;
32 using namespace NLMISC;
33 using namespace NLSOUND;
35 // Visual Studio does not support AV_TIME_BASE_Q macro in C++
36 #undef AV_TIME_BASE_Q
37 static const AVRational AV_TIME_BASE_Q = {1, AV_TIME_BASE};
39 namespace {
41 const std::string av_err2string(sint err)
43 char buf[AV_ERROR_MAX_STRING_SIZE];
44 av_strerror(err, buf, AV_ERROR_MAX_STRING_SIZE);
45 return (std::string)buf;
48 void nel_logger(void *ptr, int level, const char *fmt, va_list vargs)
50 static char msg[1024];
52 const char *module = NULL;
54 // AV_LOG_DEBUG, AV_LOG_TRACE
55 if (level >= AV_LOG_DEBUG) return;
57 if (ptr)
59 AVClass *avc = *(AVClass**) ptr;
60 module = avc->item_name(ptr);
63 vsnprintf(msg, sizeof(msg), fmt, vargs);
64 msg[sizeof(msg)-1] = '\0';
66 switch(level)
68 case AV_LOG_PANIC:
69 // ffmpeg is about to crash so lets throw
70 nlerror("FFMPEG(P): (%s) %s", module, msg);
71 break;
72 case AV_LOG_FATAL:
73 // ffmpeg had unrecoverable error, corrupted stream or such
74 nlerrornoex("FFMPEG(F): (%s) %s", module, msg);
75 break;
76 case AV_LOG_ERROR:
77 nlwarning("FFMPEG(E): (%s) %s", module, msg);
78 break;
79 case AV_LOG_WARNING:
80 nlwarning("FFMPEG(W): (%s) %s", module, msg);
81 break;
82 case AV_LOG_INFO:
83 nlinfo("FFMPEG(I): (%s) %s", module, msg);
84 break;
85 case AV_LOG_VERBOSE:
86 nldebug("FFMPEG(V): (%s) %s", module, msg);
87 break;
88 case AV_LOG_DEBUG:
89 nldebug("FFMPEG(D): (%s) %s", module, msg);
90 break;
91 default:
92 nlinfo("FFMPEG: invalid log level:%d (%s) %s", level, module, msg);
93 break;
97 class CFfmpegInstance
99 public:
100 CFfmpegInstance()
102 av_log_set_level(AV_LOG_DEBUG);
103 av_log_set_callback(nel_logger);
105 av_register_all();
107 //avformat_network_init();
110 virtual ~CFfmpegInstance()
112 //avformat_network_deinit();
116 CFfmpegInstance ffmpeg;
118 // Send bytes to ffmpeg
119 int avio_read_packet(void *opaque, uint8 *buf, int buf_size)
121 NLSOUND::CAudioDecoderFfmpeg *decoder = static_cast<NLSOUND::CAudioDecoderFfmpeg *>(opaque);
122 NLMISC::IStream *stream = decoder->getStream();
123 nlassert(stream->isReading());
125 uint32 available = decoder->getStreamSize() - stream->getPos();
126 if (available == 0) return 0;
128 buf_size = FFMIN(buf_size, available);
129 stream->serialBuffer((uint8 *)buf, buf_size);
130 return buf_size;
133 sint64 avio_seek(void *opaque, sint64 offset, int whence)
135 NLSOUND::CAudioDecoderFfmpeg *decoder = static_cast<NLSOUND::CAudioDecoderFfmpeg *>(opaque);
136 NLMISC::IStream *stream = decoder->getStream();
137 nlassert(stream->isReading());
139 NLMISC::IStream::TSeekOrigin origin;
140 switch(whence)
142 case SEEK_SET:
143 origin = NLMISC::IStream::begin;
144 break;
145 case SEEK_CUR:
146 origin = NLMISC::IStream::current;
147 break;
148 case SEEK_END:
149 origin = NLMISC::IStream::end;
150 break;
151 case AVSEEK_SIZE:
152 return decoder->getStreamSize();
153 default:
154 return -1;
157 stream->seek((sint32) offset, origin);
158 return stream->getPos();
161 }//ns
163 namespace NLSOUND {
165 // swresample will convert audio to this format
166 #define FFMPEG_SAMPLE_RATE 44100
167 #define FFMPEG_CHANNELS 2
168 #define FFMPEG_CHANNEL_LAYOUT AV_CH_LAYOUT_STEREO
169 #define FFMPEG_BITS_PER_SAMPLE 16
170 #define FFMPEG_SAMPLE_FORMAT AV_SAMPLE_FMT_S16
172 CAudioDecoderFfmpeg::CAudioDecoderFfmpeg(NLMISC::IStream *stream, bool loop)
173 : IAudioDecoder(),
174 _Stream(stream), _Loop(loop), _IsMusicEnded(false), _StreamSize(0), _IsSupported(false),
175 _AvioContext(NULL), _FormatContext(NULL),
176 _AudioContext(NULL), _AudioStreamIndex(0),
177 _SwrContext(NULL)
179 _StreamOffset = stream->getPos();
180 stream->seek(0, NLMISC::IStream::end);
181 _StreamSize = stream->getPos();
182 stream->seek(_StreamOffset, NLMISC::IStream::begin);
184 try {
185 _FormatContext = avformat_alloc_context();
186 if (!_FormatContext)
187 throw Exception("Can't create AVFormatContext");
189 // avio_ctx_buffer can be reallocated by ffmpeg and assigned to avio_ctx->buffer
190 uint8 *avio_ctx_buffer = NULL;
191 size_t avio_ctx_buffer_size = 4096;
192 avio_ctx_buffer = static_cast<uint8 *>(av_malloc(avio_ctx_buffer_size));
193 if (!avio_ctx_buffer)
194 throw Exception("Can't allocate avio context buffer");
196 _AvioContext = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, this, &avio_read_packet, NULL, &avio_seek);
197 if (!_AvioContext)
198 throw Exception("Can't allocate avio context");
200 _FormatContext->pb = _AvioContext;
201 sint ret = avformat_open_input(&_FormatContext, NULL, NULL, NULL);
202 if (ret < 0)
203 throw Exception("avformat_open_input: %d", ret);
205 // find stream and then audio codec to see if ffmpeg supports this
206 _IsSupported = false;
207 if (avformat_find_stream_info(_FormatContext, NULL) >= 0)
209 _AudioStreamIndex = av_find_best_stream(_FormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
210 if (_AudioStreamIndex >= 0)
212 _AudioContext = _FormatContext->streams[_AudioStreamIndex]->codec;
213 av_opt_set_int(_AudioContext, "refcounted_frames", 1, 0);
215 AVCodec *codec = avcodec_find_decoder(_AudioContext->codec_id);
216 if (codec != NULL && avcodec_open2(_AudioContext, codec, NULL) >= 0)
218 _IsSupported = true;
223 catch(...)
225 release();
227 throw;
230 if (!_IsSupported)
232 nlwarning("FFMPEG: Decoder created, unknown stream format / codec");
236 CAudioDecoderFfmpeg::~CAudioDecoderFfmpeg()
238 release();
241 void CAudioDecoderFfmpeg::release()
243 if (_SwrContext)
244 swr_free(&_SwrContext);
246 if (_AudioContext)
247 avcodec_close(_AudioContext);
249 if (_FormatContext)
250 avformat_close_input(&_FormatContext);
252 if (_AvioContext && _AvioContext->buffer)
253 av_freep(&_AvioContext->buffer);
255 if (_AvioContext)
256 av_freep(&_AvioContext);
259 bool CAudioDecoderFfmpeg::isFormatSupported() const
261 return _IsSupported;
264 /// Get information on a music file.
265 bool CAudioDecoderFfmpeg::getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title, float &length)
267 CAudioDecoderFfmpeg ffmpeg(stream, false);
268 if (!ffmpeg.isFormatSupported())
270 title.clear();
271 artist.clear();
272 length = 0.f;
274 return false;
277 AVDictionaryEntry *tag = NULL;
278 while((tag = av_dict_get(ffmpeg._FormatContext->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
280 if (!strcmp(tag->key, "artist"))
282 artist = tag->value;
284 else if (!strcmp(tag->key, "title"))
286 title = tag->value;
290 length = ffmpeg.getLength();
292 return true;
295 uint32 CAudioDecoderFfmpeg::getRequiredBytes()
297 return 0; // no minimum requirement of bytes to buffer out
300 uint32 CAudioDecoderFfmpeg::getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum)
302 if (_IsMusicEnded) return 0;
303 nlassert(minimum <= maximum); // can't have this..
305 // TODO: CStreamFileSource::play() will stall when there is no frames on warmup
306 // supported can be set false if there is an issue creating converter
307 if (!_IsSupported)
309 _IsMusicEnded = true;
310 return 1;
313 uint32 bytes_read = 0;
315 AVFrame frame = {0};
316 AVPacket packet = {0};
318 if (!_SwrContext)
320 sint64 in_channel_layout = av_get_default_channel_layout(_AudioContext->channels);
321 _SwrContext = swr_alloc_set_opts(NULL,
322 // output
323 FFMPEG_CHANNEL_LAYOUT, FFMPEG_SAMPLE_FORMAT, FFMPEG_SAMPLE_RATE,
324 // input
325 in_channel_layout, _AudioContext->sample_fmt, _AudioContext->sample_rate,
326 0, NULL);
327 swr_init(_SwrContext);
330 sint ret;
331 while(bytes_read < minimum)
333 // read packet from stream
334 if ((ret = av_read_frame(_FormatContext, &packet)) < 0)
336 _IsMusicEnded = true;
337 // TODO: looping
338 break;
341 if (packet.stream_index == _AudioStreamIndex)
343 // packet can contain multiple frames
344 AVPacket first = packet;
345 int got_frame = 0;
346 do {
347 got_frame = 0;
348 ret = avcodec_decode_audio4(_AudioContext, &frame, &got_frame, &packet);
349 if (ret < 0)
351 nlwarning("FFMPEG: error decoding audio frame: %s", av_err2string(ret).c_str());
352 break;
354 packet.size -= ret;
355 packet.data += ret;
357 if (got_frame)
359 uint32 out_bps = av_get_bytes_per_sample(FFMPEG_SAMPLE_FORMAT) * FFMPEG_CHANNELS;
360 uint32 max_samples = (maximum - bytes_read) / out_bps;
362 uint32 out_samples = av_rescale_rnd(swr_get_delay(_SwrContext, _AudioContext->sample_rate) + frame.nb_samples,
363 FFMPEG_SAMPLE_RATE, _AudioContext->sample_rate, AV_ROUND_UP);
365 if (max_samples > out_samples)
366 max_samples = out_samples;
368 uint32 converted = swr_convert(_SwrContext, &buffer, max_samples, (const uint8 **)frame.extended_data, frame.nb_samples);
369 uint32 size = out_bps * converted;
371 bytes_read += size;
372 buffer += size;
374 av_frame_unref(&frame);
376 } while (got_frame && packet.size > 0);
378 av_packet_unref(&first);
380 else
382 ret = 0;
383 av_packet_unref(&packet);
387 return bytes_read;
390 uint8 CAudioDecoderFfmpeg::getChannels()
392 return FFMPEG_CHANNELS;
395 uint CAudioDecoderFfmpeg::getSamplesPerSec()
397 return FFMPEG_SAMPLE_RATE;
400 uint8 CAudioDecoderFfmpeg::getBitsPerSample()
402 return FFMPEG_BITS_PER_SAMPLE;
405 bool CAudioDecoderFfmpeg::isMusicEnded()
407 return _IsMusicEnded;
410 float CAudioDecoderFfmpeg::getLength()
412 float length = 0.f;
413 if (_FormatContext->duration != AV_NOPTS_VALUE)
415 length = _FormatContext->duration * av_q2d(AV_TIME_BASE_Q);
417 else if (_FormatContext->streams[_AudioStreamIndex]->duration != AV_NOPTS_VALUE)
419 length = _FormatContext->streams[_AudioStreamIndex]->duration * av_q2d(_FormatContext->streams[_AudioStreamIndex]->time_base);
421 return length;
424 void CAudioDecoderFfmpeg::setLooping(bool loop)
426 _Loop = loop;
429 } /* namespace NLSOUND */
431 /* end of file */