ffaudio-core.cc
| 1 |
/*
|
|---|---|
| 2 |
* Audacious FFaudio Plugin
|
| 3 |
* Copyright © 2009 William Pitcock <nenolod@dereferenced.org>
|
| 4 |
* Matti Hämäläinen <ccr@tnsp.org>
|
| 5 |
* Copyright © 2011 John Lindgren <john.lindgren@tds.net>
|
| 6 |
* Video-playing capability added Copyright © 2015 Jim Turner <turnerjw784@yahoo.com>
|
| 7 |
*
|
| 8 |
* Redistribution and use in source and binary forms, with or without
|
| 9 |
* modification, are permitted provided that the following conditions are met:
|
| 10 |
*
|
| 11 |
* 1. Redistributions of source code must retain the above copyright notice,
|
| 12 |
* this list of conditions, and the following disclaimer.
|
| 13 |
*
|
| 14 |
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 15 |
* this list of conditions, and the following disclaimer in the documentation
|
| 16 |
* provided with the distribution.
|
| 17 |
*
|
| 18 |
* This software is provided "as is" and without any warranty, express or
|
| 19 |
* implied. In no event shall the authors be liable for any damages arising from
|
| 20 |
*/
|
| 21 |
|
| 22 |
#include <stdlib.h> |
| 23 |
#include <stdio.h> |
| 24 |
#include <string.h> |
| 25 |
|
| 26 |
#include <pthread.h> |
| 27 |
|
| 28 |
#undef FFAUDIO_DOUBLECHECK /* Doublecheck probing result for debugging purposes */ |
| 29 |
#undef FFAUDIO_NO_BLACKLIST /* Don't blacklist any recognized codecs/formats */ |
| 30 |
|
| 31 |
#include "ffaudio-stdinc.h" |
| 32 |
|
| 33 |
#include <audacious/audtag.h> |
| 34 |
#include <libaudcore/audstrings.h> |
| 35 |
#include <libaudcore/i18n.h> |
| 36 |
#include <libaudcore/multihash.h> |
| 37 |
#include <libaudcore/runtime.h> |
| 38 |
|
| 39 |
#include <stdint.h> |
| 40 |
#include "libavutil/common.h" |
| 41 |
#include "libavutil/dict.h" |
| 42 |
#include "libavutil/log.h" |
| 43 |
#include "libavformat/version.h" |
| 44 |
#include "libavformat/avio.h" |
| 45 |
extern "C" { |
| 46 |
#include <SDL_syswm.h> |
| 47 |
#include <libswscale/swscale.h> |
| 48 |
} |
| 49 |
#include <X11/Xlib.h> |
| 50 |
#include <SDL.h> |
| 51 |
#include <SDL_thread.h> |
| 52 |
|
| 53 |
#define SDL_AUDIO_BUFFER_SIZE 4096 |
| 54 |
#define MAX_AUDIO_FRAME_SIZE 192000 |
| 55 |
|
| 56 |
|
| 57 |
class FFaudio : public InputPlugin |
| 58 |
{
|
| 59 |
public:
|
| 60 |
static const char about[]; |
| 61 |
static const char * const exts[], * const mimes[]; |
| 62 |
|
| 63 |
static constexpr PluginInfo info = { |
| 64 |
N_("FFmpeg Plugin"),
|
| 65 |
PACKAGE, |
| 66 |
about |
| 67 |
}; |
| 68 |
|
| 69 |
static constexpr auto iinfo = InputInfo (FlagWritesTag) |
| 70 |
.with_priority (10) /* lowest priority fallback */ |
| 71 |
.with_exts (exts) |
| 72 |
.with_mimes (mimes); |
| 73 |
|
| 74 |
constexpr FFaudio () : InputPlugin (info, iinfo) {}
|
| 75 |
|
| 76 |
bool init ();
|
| 77 |
void cleanup ();
|
| 78 |
|
| 79 |
bool is_our_file (const char * filename, VFSFile & file); |
| 80 |
Tuple read_tuple (const char * filename, VFSFile & file); |
| 81 |
Index<char> read_image (const char * filename, VFSFile & file); |
| 82 |
bool write_tuple (const char * filename, VFSFile & file, const Tuple & tuple); |
| 83 |
bool play (const char * filename, VFSFile & file); |
| 84 |
}; |
| 85 |
|
| 86 |
EXPORT FFaudio aud_plugin_instance; |
| 87 |
|
| 88 |
static bool play_video; /* JWT: TRUE IF USER IS CURRENTLY PLAYING VIDEO (KILLING VID. WINDOW TURNS OFF! */ |
| 89 |
|
| 90 |
typedef struct |
| 91 |
{
|
| 92 |
int stream_idx;
|
| 93 |
AVStream * stream; |
| 94 |
AVCodecContext * context; |
| 95 |
AVCodec * codec; |
| 96 |
} |
| 97 |
CodecInfo; |
| 98 |
|
| 99 |
/*
|
| 100 |
JWT: ADDED ALL THIS QUEUE STUFF TO SMOOTH VIDEO PERFORMANCE SO THAT VIDEO FRAMES WOULD
|
| 101 |
BE OUTPUT MORE INTERLACED WITH THE AUDIO FRAMES BY QUEUEING VIDEO FRAMES UNTIL AN
|
| 102 |
AUDIO FRAME IS PROCESSED, THEN DEQUEUEING AND PROCESSING 'EM WITH EACH AUDIO FRAME.
|
| 103 |
THE SIZE OF THIS QUEUE IS SET BY video_qsize CONFIG PARAMETER AND DEFAULTS TO 16.
|
| 104 |
HAVING TOO MANY CAN RESULT IN DELAYED VIDEO, SO EXPERIMENT. IDEALLY, PACKETS SHOULD
|
| 105 |
BE PROCESSED: V A V A V A..., BUT THIS HANDLES:
|
| 106 |
V1 V2 V3 V4 V5 A1 A2 A3 A4 A5 A6 A7 V7 A8... AS:
|
| 107 |
(q:V1 V2 V3 V4 V5 V6) A1 A2 dq:V1 A3 A4 dq:V2 A5 A6 dq:V3 A7 A8...
|
| 108 |
WE DON'T WANT TO INTERRUPT AUDIO PERFORMANCE AND I DON'T KNOW HOW TO THREAD IT UP,
|
| 109 |
BUT THIS SIMPLE APPROACH SEEMS TO WORK PRETTY SMOOTH FOR ME! OTHERWISE TRY
|
| 110 |
INCREASING video_qsize IN config file OTHERWISE.
|
| 111 |
BORROWED THESE FUNCTIONS FROM:
|
| 112 |
http://www.thelearningpoint.net/computer-science/data-structures-queues--with-c-program-source-code
|
| 113 |
*/
|
| 114 |
|
| 115 |
typedef struct |
| 116 |
{
|
| 117 |
int capacity;
|
| 118 |
int size;
|
| 119 |
int front;
|
| 120 |
int rear;
|
| 121 |
AVPacket *elements; |
| 122 |
} |
| 123 |
pktQueue; |
| 124 |
|
| 125 |
pktQueue * createQueue (int maxElements)
|
| 126 |
{
|
| 127 |
/* Create a Queue */
|
| 128 |
pktQueue *Q; |
| 129 |
Q = (pktQueue *)malloc(sizeof(pktQueue));
|
| 130 |
/* Initialise its properties */
|
| 131 |
Q->elements = (AVPacket *)malloc(sizeof(AVPacket)*maxElements);
|
| 132 |
Q->size = 0;
|
| 133 |
Q->capacity = maxElements; |
| 134 |
Q->front = 0;
|
| 135 |
Q->rear = -1;
|
| 136 |
/* Return the pointer */
|
| 137 |
return Q;
|
| 138 |
} |
| 139 |
|
| 140 |
bool Dequeue (pktQueue *Q)
|
| 141 |
{
|
| 142 |
/* If Queue size is zero then it is empty. So we cannot pop */
|
| 143 |
if(Q->size==0) |
| 144 |
return false; |
| 145 |
/* Removing an element is equivalent to incrementing index of front by one */
|
| 146 |
else
|
| 147 |
{
|
| 148 |
Q->size--; |
| 149 |
if (Q->elements[Q->front].data)
|
| 150 |
av_free_packet(&Q->elements[Q->front]); |
| 151 |
|
| 152 |
Q->front++; |
| 153 |
/* As we fill elements in circular fashion */
|
| 154 |
if(Q->front==Q->capacity)
|
| 155 |
Q->front=0;
|
| 156 |
} |
| 157 |
return true; |
| 158 |
} |
| 159 |
|
| 160 |
/* JWT:FLUSH AND FREE EVERYTHING IN THE QUEUE */
|
| 161 |
void QFlush (pktQueue *Q)
|
| 162 |
{
|
| 163 |
while (Q->size > 0) |
| 164 |
{
|
| 165 |
Q->size--; |
| 166 |
if (Q->elements[Q->front].data)
|
| 167 |
av_free_packet(&Q->elements[Q->front]); |
| 168 |
|
| 169 |
Q->front++; |
| 170 |
/* As we fill elements in circular fashion */
|
| 171 |
if(Q->front==Q->capacity)
|
| 172 |
Q->front=0;
|
| 173 |
} |
| 174 |
|
| 175 |
} |
| 176 |
|
| 177 |
AVPacket * QFront (pktQueue *Q) |
| 178 |
{
|
| 179 |
if(Q->size==0) |
| 180 |
{
|
| 181 |
AUDDBG("Queue is Empty\n");
|
| 182 |
return nullptr; |
| 183 |
} |
| 184 |
/* Return the element which is at the front*/
|
| 185 |
return &Q->elements[Q->front];
|
| 186 |
} |
| 187 |
|
| 188 |
bool isQueueFull (pktQueue *Q)
|
| 189 |
{
|
| 190 |
return (Q->size == Q->capacity) ? true : false; |
| 191 |
} |
| 192 |
|
| 193 |
bool Enqueue (pktQueue *Q, AVPacket element)
|
| 194 |
{
|
| 195 |
/* If the Queue is full, we cannot push an element into it as there is no space for it.*/
|
| 196 |
if(Q->size == Q->capacity)
|
| 197 |
{
|
| 198 |
printf("Queue is Full\n");
|
| 199 |
return false; |
| 200 |
} |
| 201 |
else
|
| 202 |
{
|
| 203 |
Q->size++; |
| 204 |
Q->rear = Q->rear + 1;
|
| 205 |
/* As we fill the queue in circular fashion */
|
| 206 |
if(Q->rear == Q->capacity)
|
| 207 |
{
|
| 208 |
Q->rear = 0;
|
| 209 |
} |
| 210 |
/* Insert the element in its rear side */
|
| 211 |
Q->elements[Q->rear] = element; |
| 212 |
} |
| 213 |
return true; |
| 214 |
} |
| 215 |
|
| 216 |
/* JWT:END OF ADDED VIDEO PACKET QUEUEING FUNCTIONS */
|
| 217 |
|
| 218 |
static SimpleHash<String, AVInputFormat *> extension_dict;
|
| 219 |
|
| 220 |
static void create_extension_dict (); |
| 221 |
|
| 222 |
static int lockmgr (void * * mutexp, enum AVLockOp op) |
| 223 |
{
|
| 224 |
switch (op)
|
| 225 |
{
|
| 226 |
case AV_LOCK_CREATE:
|
| 227 |
* mutexp = new pthread_mutex_t;
|
| 228 |
pthread_mutex_init ((pthread_mutex_t *) * mutexp, nullptr);
|
| 229 |
break;
|
| 230 |
case AV_LOCK_OBTAIN:
|
| 231 |
pthread_mutex_lock ((pthread_mutex_t *) * mutexp); |
| 232 |
break;
|
| 233 |
case AV_LOCK_RELEASE:
|
| 234 |
pthread_mutex_unlock ((pthread_mutex_t *) * mutexp); |
| 235 |
break;
|
| 236 |
case AV_LOCK_DESTROY:
|
| 237 |
pthread_mutex_destroy ((pthread_mutex_t *) * mutexp); |
| 238 |
delete (pthread_mutex_t *) * mutexp;
|
| 239 |
break;
|
| 240 |
} |
| 241 |
|
| 242 |
return 0; |
| 243 |
} |
| 244 |
|
| 245 |
static void ffaudio_log_cb (void * avcl, int av_level, const char * fmt, va_list va) |
| 246 |
{
|
| 247 |
audlog::Level level = audlog::Debug; |
| 248 |
char message [2048]; |
| 249 |
|
| 250 |
switch (av_level)
|
| 251 |
{
|
| 252 |
case AV_LOG_QUIET:
|
| 253 |
return;
|
| 254 |
case AV_LOG_PANIC:
|
| 255 |
case AV_LOG_FATAL:
|
| 256 |
case AV_LOG_ERROR:
|
| 257 |
level = audlog::Error; |
| 258 |
break;
|
| 259 |
case AV_LOG_WARNING:
|
| 260 |
level = audlog::Warning; |
| 261 |
break;
|
| 262 |
case AV_LOG_INFO:
|
| 263 |
level = audlog::Info; |
| 264 |
break;
|
| 265 |
default:
|
| 266 |
break;
|
| 267 |
} |
| 268 |
|
| 269 |
AVClass * avc = avcl ? * (AVClass * *) avcl : nullptr;
|
| 270 |
|
| 271 |
vsnprintf (message, sizeof message, fmt, va);
|
| 272 |
|
| 273 |
audlog::log (level, __FILE__, __LINE__, avc ? avc->item_name(avcl) : __FUNCTION__, |
| 274 |
"<%p> %s", avcl, message);
|
| 275 |
} |
| 276 |
|
| 277 |
bool FFaudio::init ()
|
| 278 |
{
|
| 279 |
av_register_all(); |
| 280 |
av_lockmgr_register (lockmgr); |
| 281 |
|
| 282 |
create_extension_dict (); |
| 283 |
|
| 284 |
av_log_set_callback (ffaudio_log_cb); |
| 285 |
|
| 286 |
return true; |
| 287 |
} |
| 288 |
|
| 289 |
void FFaudio::cleanup ()
|
| 290 |
{
|
| 291 |
extension_dict.clear (); |
| 292 |
|
| 293 |
av_lockmgr_register (nullptr);
|
| 294 |
} |
| 295 |
|
| 296 |
static const char * ffaudio_strerror (int error) |
| 297 |
{
|
| 298 |
static char buf[256]; |
| 299 |
return (! av_strerror (error, buf, sizeof buf)) ? buf : "unknown error"; |
| 300 |
} |
| 301 |
|
| 302 |
static void create_extension_dict () |
| 303 |
{
|
| 304 |
AVInputFormat * f; |
| 305 |
for (f = av_iformat_next (nullptr); f; f = av_iformat_next (f)) |
| 306 |
{
|
| 307 |
if (! f->extensions)
|
| 308 |
continue;
|
| 309 |
|
| 310 |
StringBuf exts = str_tolower (f->extensions); |
| 311 |
Index<String> extlist = str_list_to_index (exts, ",");
|
| 312 |
|
| 313 |
for (auto & ext : extlist) |
| 314 |
extension_dict.add (ext, std::move (f)); |
| 315 |
} |
| 316 |
} |
| 317 |
|
| 318 |
static AVInputFormat * get_format_by_extension (const char * name) |
| 319 |
{
|
| 320 |
StringBuf ext = uri_get_extension (name); |
| 321 |
if (! ext)
|
| 322 |
return nullptr; |
| 323 |
|
| 324 |
AUDDBG ("Get format by extension: %s\n", name);
|
| 325 |
AVInputFormat * * f = extension_dict.lookup (String (str_tolower (ext))); |
| 326 |
|
| 327 |
if (f && * f)
|
| 328 |
AUDDBG ("Format %s.\n", (* f)->name);
|
| 329 |
else
|
| 330 |
AUDDBG ("Format unknown.\n");
|
| 331 |
|
| 332 |
return f ? * f : nullptr; |
| 333 |
} |
| 334 |
|
| 335 |
static AVInputFormat * get_format_by_content (const char * name, VFSFile & file) |
| 336 |
{
|
| 337 |
AUDDBG ("Get format by content: %s\n", name);
|
| 338 |
|
| 339 |
AVInputFormat * f = nullptr;
|
| 340 |
|
| 341 |
unsigned char buf[16384 + AVPROBE_PADDING_SIZE]; |
| 342 |
int size = 16; |
| 343 |
int filled = 0; |
| 344 |
int target = 100; |
| 345 |
int score = 0; |
| 346 |
|
| 347 |
while (1) |
| 348 |
{
|
| 349 |
if (filled < size)
|
| 350 |
filled += file.fread (buf + filled, 1, size - filled);
|
| 351 |
|
| 352 |
memset (buf + filled, 0, AVPROBE_PADDING_SIZE);
|
| 353 |
AVProbeData d = {name, buf, filled};
|
| 354 |
score = target; |
| 355 |
|
| 356 |
f = av_probe_input_format2 (& d, true, & score);
|
| 357 |
if (f)
|
| 358 |
break;
|
| 359 |
|
| 360 |
if (size < 16384 && filled == size) |
| 361 |
size *= 4;
|
| 362 |
else if (target > 10) |
| 363 |
target = 10;
|
| 364 |
else
|
| 365 |
break;
|
| 366 |
} |
| 367 |
|
| 368 |
if (f)
|
| 369 |
AUDDBG ("Format %s, buffer size %d, score %d.\n", f->name, filled, score);
|
| 370 |
else
|
| 371 |
AUDDBG ("Format unknown.\n");
|
| 372 |
|
| 373 |
if (file.fseek (0, VFS_SEEK_SET) < 0) |
| 374 |
; /* ignore errors here */
|
| 375 |
|
| 376 |
return f;
|
| 377 |
} |
| 378 |
|
| 379 |
static AVInputFormat * get_format (const char * name, VFSFile & file) |
| 380 |
{
|
| 381 |
AVInputFormat * f = get_format_by_extension (name); |
| 382 |
return f ? f : get_format_by_content (name, file);
|
| 383 |
} |
| 384 |
|
| 385 |
static AVFormatContext * open_input_file (const char * name, VFSFile & file) |
| 386 |
{
|
| 387 |
int ret;
|
| 388 |
AVFormatContext * c; |
| 389 |
|
| 390 |
play_video = aud_get_bool ("ffaudio", "play_video"); /* JWT:RESET PLAY-VIDEO, CASE TURNED OFF ON PREV. PLAY. */ |
| 391 |
if (!strcmp(name, "-") || strstr(name, "://-.")) |
| 392 |
{
|
| 393 |
AUDDBG("-open_input_file(stdin)\n");
|
| 394 |
c = NULL;
|
| 395 |
const char * xname = "pipe:"; |
| 396 |
ret = avformat_open_input (&c, xname, NULL, NULL); |
| 397 |
if (ret < 0) |
| 398 |
{
|
| 399 |
AUDERR ("avformat_open_input failed for %s: %s.\n", xname, ffaudio_strerror (ret));
|
| 400 |
return nullptr; |
| 401 |
} |
| 402 |
} |
| 403 |
else
|
| 404 |
{
|
| 405 |
AUDDBG("-open_input_file(%s)\n", name);
|
| 406 |
AVInputFormat * f = get_format (name, file); |
| 407 |
|
| 408 |
if (! f)
|
| 409 |
{
|
| 410 |
AUDERR ("Unknown format for %s.\n", name);
|
| 411 |
return nullptr; |
| 412 |
} |
| 413 |
|
| 414 |
AVFormatContext * c = avformat_alloc_context (); |
| 415 |
AVIOContext * io = io_context_new (file); |
| 416 |
c->pb = io; |
| 417 |
|
| 418 |
ret = avformat_open_input (&c, name, f, nullptr);
|
| 419 |
if (ret < 0) |
| 420 |
{
|
| 421 |
AUDERR ("avformat_open_input failed for %s: %s.\n", name, ffaudio_strerror (ret));
|
| 422 |
io_context_free (io); |
| 423 |
return nullptr; |
| 424 |
} |
| 425 |
} |
| 426 |
|
| 427 |
AUDDBG("-open_input_file - success!\n");
|
| 428 |
return c;
|
| 429 |
} |
| 430 |
|
| 431 |
static void close_input_file (AVFormatContext * c, bool fromstdin) |
| 432 |
{
|
| 433 |
AVIOContext * io = NULL;
|
| 434 |
|
| 435 |
if (!fromstdin)
|
| 436 |
io = c->pb; |
| 437 |
|
| 438 |
#if CHECK_LIBAVFORMAT_VERSION (53, 25, 0, 53, 17, 0) |
| 439 |
avformat_close_input (&c); |
| 440 |
#else
|
| 441 |
av_close_input_file (c); |
| 442 |
#endif
|
| 443 |
|
| 444 |
if (!fromstdin)
|
| 445 |
io_context_free (io); |
| 446 |
} |
| 447 |
|
| 448 |
static bool find_codec (AVFormatContext * c, CodecInfo * cinfo, CodecInfo * vcinfo) |
| 449 |
{
|
| 450 |
int videoStream;
|
| 451 |
int audioStream;
|
| 452 |
avformat_find_stream_info (c, NULL);
|
| 453 |
|
| 454 |
videoStream=-1;
|
| 455 |
audioStream=-1;
|
| 456 |
for (unsigned i = 0; i < c->nb_streams; i++) |
| 457 |
{
|
| 458 |
if (c->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
|
| 459 |
videoStream=i; |
| 460 |
else if (c->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO && audioStream < 0) |
| 461 |
audioStream=i; |
| 462 |
} |
| 463 |
if (audioStream==-1) /* PUNT IF NO AUDIO SINCE AUDACIOUS IS AN *AUDIO* PLAYER! */ |
| 464 |
return false; |
| 465 |
|
| 466 |
AVCodec * codec = avcodec_find_decoder (c->streams[audioStream]->codec->codec_id); |
| 467 |
if (codec)
|
| 468 |
{
|
| 469 |
cinfo->stream_idx = audioStream; |
| 470 |
cinfo->stream = c->streams[audioStream]; |
| 471 |
cinfo->context = c->streams[audioStream]->codec; |
| 472 |
cinfo->codec = codec; |
| 473 |
|
| 474 |
/* JWT: NOW IF USER WANTS VIDEO, SEE IF WE GOT A VIDEO STREAM TOO: */
|
| 475 |
if (play_video && videoStream >= 0) |
| 476 |
{
|
| 477 |
AVCodec * vcodec = avcodec_find_decoder (c->streams[videoStream]->codec->codec_id); |
| 478 |
if (vcodec)
|
| 479 |
{
|
| 480 |
vcinfo->stream_idx = videoStream; |
| 481 |
vcinfo->stream = c->streams[videoStream]; |
| 482 |
vcinfo->context = c->streams[videoStream]->codec; |
| 483 |
vcinfo->codec = vcodec; |
| 484 |
} |
| 485 |
} |
| 486 |
else
|
| 487 |
play_video = false; /* turn off video playback, since we could not find stream! */ |
| 488 |
|
| 489 |
return true; |
| 490 |
} |
| 491 |
|
| 492 |
return false; |
| 493 |
} |
| 494 |
|
| 495 |
bool FFaudio::is_our_file (const char * filename, VFSFile & file) |
| 496 |
{
|
| 497 |
return (bool) get_format (filename, file); |
| 498 |
} |
| 499 |
|
| 500 |
static const struct { |
| 501 |
Tuple::ValueType ttype; /* Tuple field value type */
|
| 502 |
Tuple::Field field; /* Tuple field constant */
|
| 503 |
const char * keys[5]; /* Keys to match (case-insensitive), ended by nullptr */ |
| 504 |
} metaentries[] = {
|
| 505 |
{Tuple::String, Tuple::Artist, {"author", "hor", "artist", nullptr}},
|
| 506 |
{Tuple::String, Tuple::Title, {"title", "le", nullptr}},
|
| 507 |
{Tuple::String, Tuple::Album, {"album", "WM/AlbumTitle", nullptr}},
|
| 508 |
{Tuple::String, Tuple::Performer, {"performer", nullptr}},
|
| 509 |
{Tuple::String, Tuple::Copyright, {"copyright", nullptr}},
|
| 510 |
{Tuple::String, Tuple::Genre, {"genre", "WM/Genre", nullptr}},
|
| 511 |
{Tuple::String, Tuple::Comment, {"comment", nullptr}},
|
| 512 |
{Tuple::String, Tuple::Composer, {"composer", nullptr}},
|
| 513 |
{Tuple::Int, Tuple::Year, {"year", "WM/Year", "date", nullptr}},
|
| 514 |
{Tuple::Int, Tuple::Track, {"track", "WM/TrackNumber", nullptr}},
|
| 515 |
}; |
| 516 |
|
| 517 |
static void read_metadata_dict (Tuple & tuple, AVDictionary * dict) |
| 518 |
{
|
| 519 |
for (auto & meta : metaentries) |
| 520 |
{
|
| 521 |
AVDictionaryEntry * entry = nullptr;
|
| 522 |
|
| 523 |
for (int j = 0; ! entry && meta.keys[j]; j ++) |
| 524 |
entry = av_dict_get (dict, meta.keys[j], nullptr, 0); |
| 525 |
|
| 526 |
if (entry && entry->value)
|
| 527 |
{
|
| 528 |
if (meta.ttype == Tuple::String)
|
| 529 |
tuple.set_str (meta.field, entry->value); |
| 530 |
else if (meta.ttype == Tuple::Int) |
| 531 |
tuple.set_int (meta.field, atoi (entry->value)); |
| 532 |
} |
| 533 |
} |
| 534 |
} |
| 535 |
|
| 536 |
Tuple FFaudio::read_tuple (const char * filename, VFSFile & file) |
| 537 |
{
|
| 538 |
Tuple tuple; |
| 539 |
bool fromstdin;
|
| 540 |
|
| 541 |
fromstdin = (!strcmp(filename, "-") || strstr(filename, "://-.")) ? true : false; |
| 542 |
AUDDBG("Filename =%s=\n", filename);
|
| 543 |
if (fromstdin)
|
| 544 |
tuple.set_filename (filename); |
| 545 |
else /* JWT:THIS STUFF DEFERRED UNTIL PLAY() FOR STDIN, BUT SEEMS TO HAVE TO BE HERE FOR DIRECT */ |
| 546 |
{
|
| 547 |
AVFormatContext * ic = open_input_file (filename, file); |
| 548 |
|
| 549 |
if (ic)
|
| 550 |
{
|
| 551 |
CodecInfo cinfo, vcinfo; |
| 552 |
|
| 553 |
if (find_codec (ic, & cinfo, & vcinfo))
|
| 554 |
{
|
| 555 |
tuple.set_filename (filename); |
| 556 |
|
| 557 |
tuple.set_int (Tuple::Length, ic->duration / 1000);
|
| 558 |
tuple.set_int (Tuple::Bitrate, ic->bit_rate / 1000);
|
| 559 |
if (cinfo.codec->long_name)
|
| 560 |
tuple.set_str (Tuple::Codec, cinfo.codec->long_name); |
| 561 |
if (ic->metadata)
|
| 562 |
read_metadata_dict (tuple, ic->metadata); |
| 563 |
if (cinfo.stream->metadata)
|
| 564 |
read_metadata_dict (tuple, cinfo.stream->metadata); |
| 565 |
if (play_video && vcinfo.stream->metadata)
|
| 566 |
read_metadata_dict (tuple, vcinfo.stream->metadata); |
| 567 |
} |
| 568 |
|
| 569 |
close_input_file (ic, fromstdin); |
| 570 |
} |
| 571 |
|
| 572 |
if (tuple && ! file.fseek (0, VFS_SEEK_SET)) |
| 573 |
audtag::tuple_read (tuple, file); |
| 574 |
/* JWT:BUILD NOTE: IF USING LATEST AUDACIOUS VERSIONS YOU MAY NEED TO REPLACE ABOVE LINE WITH:
|
| 575 |
audtag::read_tag (file, & tuple, nullptr);
|
| 576 |
*/
|
| 577 |
} |
| 578 |
|
| 579 |
return tuple;
|
| 580 |
} |
| 581 |
|
| 582 |
bool FFaudio::write_tuple (const char * filename, VFSFile & file, const Tuple & tuple) |
| 583 |
{
|
| 584 |
if (str_has_suffix_nocase (filename, ".ape")) |
| 585 |
return audtag::tuple_write (tuple, file, audtag::TagType::APE);
|
| 586 |
|
| 587 |
/* return audtag::tuple_write (tuple, file, audtag::TagType::None); */
|
| 588 |
/* JWT:FIXME: FOR SOME REASON ABOVE LINE WON'T COMPILE WITH SDL_syswm.h AND X11/Xlib.h INCLUDED?!?!
|
| 589 |
"error: expected unqualified-id before numeric constant return audtag::tuple_write (tuple, file, audtag::TagType::None);"
|
| 590 |
SO REPLACED WITH NEXT LINE:
|
| 591 |
*/
|
| 592 |
return audtag::tuple_write (tuple, file, audtag::TagType::ID3v2);
|
| 593 |
} |
| 594 |
|
| 595 |
Index<char> FFaudio::read_image (const char * filename, VFSFile & file) |
| 596 |
{
|
| 597 |
if (str_has_suffix_nocase (filename, ".m4a") || str_has_suffix_nocase (filename, ".mp4")) |
| 598 |
return read_itunes_cover (filename, file);
|
| 599 |
|
| 600 |
return Index<char> (); |
| 601 |
} |
| 602 |
|
| 603 |
/* JWT: NEW FUNCTION TO WRITE VIDEO FRAMES TO THE POPUP WINDOW: */
|
| 604 |
void write_videoframe (SwsContext * sws_ctx, CodecInfo * cinfo, CodecInfo * vcinfo,
|
| 605 |
AVFrame * vframe, SDL_Overlay * bmp, AVPacket *pkt, int video_width, int video_height) |
| 606 |
{
|
| 607 |
SDL_Rect rect; |
| 608 |
|
| 609 |
int len = 0; |
| 610 |
int frameFinished = 0; |
| 611 |
while (1) |
| 612 |
{
|
| 613 |
len = avcodec_decode_video2 (vcinfo->context, vframe, &frameFinished, pkt); |
| 614 |
/* Did we get a video frame? */
|
| 615 |
if (len < 0) |
| 616 |
{
|
| 617 |
AUDERR ("decode_video() failed, code %d\n", len);
|
| 618 |
return;
|
| 619 |
} |
| 620 |
else if (frameFinished) |
| 621 |
{
|
| 622 |
AVPicture pict = { { 0 } };
|
| 623 |
SDL_LockYUVOverlay(bmp); |
| 624 |
pict.data[0] = bmp->pixels[0]; |
| 625 |
pict.data[1] = bmp->pixels[2]; |
| 626 |
pict.data[2] = bmp->pixels[1]; |
| 627 |
pict.linesize[0] = bmp->pitches[0]; |
| 628 |
pict.linesize[1] = bmp->pitches[2]; |
| 629 |
pict.linesize[2] = bmp->pitches[1]; |
| 630 |
/* Convert the image into YUV format that SDL uses. */
|
| 631 |
sws_scale(sws_ctx, (uint8_t const * const *)vframe->data, |
| 632 |
vframe->linesize, 0, vcinfo->context->height,
|
| 633 |
pict.data, pict.linesize); |
| 634 |
SDL_UnlockYUVOverlay(bmp); |
| 635 |
|
| 636 |
rect.x = 0;
|
| 637 |
rect.y = 0;
|
| 638 |
rect.w = video_width; |
| 639 |
rect.h = video_height; |
| 640 |
SDL_DisplayYUVOverlay(bmp, &rect); |
| 641 |
AUDDBG("+++++Process video packet\n");
|
| 642 |
return;
|
| 643 |
} |
| 644 |
} |
| 645 |
} |
| 646 |
|
| 647 |
/* JWT: (*nix ONLY) NEW FUNCTION TO SAVE POPUP VIDEO WINDOW'S LOCATION TO CONFIG SO IT POPS UP THERE NEXT TIME! */
|
| 648 |
/* FIXME:NEED DIFFERENT CODE TO ADD THIS FEATURE TO WINDOWS! */
|
| 649 |
void save_window_xy ()
|
| 650 |
{
|
| 651 |
#ifndef _WINDOWS
|
| 652 |
SDL_SysWMinfo info; |
| 653 |
SDL_VERSION(&info.version); |
| 654 |
if (SDL_GetWMInfo(&info) > 0) |
| 655 |
{
|
| 656 |
int x=0,y=0; |
| 657 |
Window chldwin; |
| 658 |
XWindowAttributes attr0, attr; |
| 659 |
info.info.x11.lock_func(); |
| 660 |
XGetWindowAttributes(info.info.x11.display, info.info.x11.wmwindow, &attr0); |
| 661 |
XGetWindowAttributes(info.info.x11.display, info.info.x11.window, &attr); |
| 662 |
XTranslateCoordinates(info.info.x11.display, info.info.x11.window, attr.root, attr.x, attr.y, &x, &y, &chldwin); |
| 663 |
info.info.x11.unlock_func(); |
| 664 |
aud_set_int ("ffaudio", "video_window_x", (x-attr0.x)-1); |
| 665 |
aud_set_int ("ffaudio", "video_window_y", (y-attr0.y)-1); |
| 666 |
aud_set_int ("ffaudio", "video_window_w", attr.width); |
| 667 |
aud_set_int ("ffaudio", "video_window_h", attr.height); |
| 668 |
} |
| 669 |
#endif
|
| 670 |
} |
| 671 |
bool FFaudio::play (const char * filename, VFSFile & file) |
| 672 |
{
|
| 673 |
AUDDBG ("Playing %s.\n", filename);
|
| 674 |
|
| 675 |
AVPacket pkt = AVPacket(); |
| 676 |
int errcount;
|
| 677 |
bool codec_opened = false; |
| 678 |
bool vcodec_opened = false; |
| 679 |
int out_fmt;
|
| 680 |
bool planar;
|
| 681 |
bool error;
|
| 682 |
SDL_Overlay *bmp; /* JWT: ALL SDL_* STUFF IS FOR PLAYING VIDEOS */
|
| 683 |
SDL_Surface *screen; |
| 684 |
SDL_Event event; |
| 685 |
int video_width;
|
| 686 |
int video_height;
|
| 687 |
int video_window_x;
|
| 688 |
int video_window_y;
|
| 689 |
int video_window_w;
|
| 690 |
int video_window_h;
|
| 691 |
float video_aspect_ratio;
|
| 692 |
int video_resizedelay;
|
| 693 |
bool fromstdin; /* JWT:SAVE (static)fromstdin's STATE AT START OF PLAY, SINCE PROBES WILL CHANGE IT IN PLAYLIST ADVANCE BEFORE WE CLOSE! */ |
| 694 |
struct SwsContext *sws_ctx;
|
| 695 |
Index<char> buf;
|
| 696 |
|
| 697 |
video_width = 0;
|
| 698 |
video_height = 0;
|
| 699 |
video_window_x = 0;
|
| 700 |
video_window_y = 0;
|
| 701 |
video_window_w = 0;
|
| 702 |
video_window_h = 0;
|
| 703 |
video_aspect_ratio = 0;
|
| 704 |
video_resizedelay = aud_get_int ("ffaudio", "video_resizedelay"); |
| 705 |
|
| 706 |
fromstdin = (!strcmp(filename, "-") || strstr(filename, "://-.")) ? true : false; |
| 707 |
error = false;
|
| 708 |
bmp = nullptr;
|
| 709 |
screen = nullptr;
|
| 710 |
sws_ctx = NULL;
|
| 711 |
AVFormatContext * ic = open_input_file (filename, file); |
| 712 |
if (! ic)
|
| 713 |
return false; |
| 714 |
|
| 715 |
CodecInfo cinfo, vcinfo; |
| 716 |
|
| 717 |
if (fromstdin) /* JWT: FOR STDIN: TRY TO GET "read_tuple()" STUFF NOW, SINCE FILE COULD NOT BE OPENED EARLIER IN read_tuple()! */ |
| 718 |
{
|
| 719 |
Tuple tuple; |
| 720 |
|
| 721 |
AUDDBG ("---- playing from STDIN: get TUPLE stuff now: IC is defined\n");
|
| 722 |
if (find_codec (ic, & cinfo, & vcinfo))
|
| 723 |
{
|
| 724 |
tuple.set_filename (filename); |
| 725 |
|
| 726 |
tuple.set_int (Tuple::Length, ic->duration / 1000);
|
| 727 |
tuple.set_int (Tuple::Bitrate, ic->bit_rate / 1000);
|
| 728 |
if (cinfo.codec->long_name)
|
| 729 |
tuple.set_str (Tuple::Codec, cinfo.codec->long_name); |
| 730 |
if (ic->metadata)
|
| 731 |
read_metadata_dict (tuple, ic->metadata); |
| 732 |
if (cinfo.stream->metadata)
|
| 733 |
read_metadata_dict (tuple, cinfo.stream->metadata); |
| 734 |
if (play_video && vcinfo.stream->metadata)
|
| 735 |
read_metadata_dict (tuple, vcinfo.stream->metadata); |
| 736 |
set_playback_tuple (tuple.ref ()); |
| 737 |
} |
| 738 |
else
|
| 739 |
{
|
| 740 |
AUDERR ("No codec found for %s.\n", filename);
|
| 741 |
goto error_exit;
|
| 742 |
} |
| 743 |
} |
| 744 |
else
|
| 745 |
{
|
| 746 |
if (! find_codec (ic, & cinfo, & vcinfo))
|
| 747 |
{
|
| 748 |
AUDERR ("No codec found for %s.\n", filename);
|
| 749 |
goto error_exit;
|
| 750 |
} |
| 751 |
} |
| 752 |
|
| 753 |
AUDDBG ("got codec %s for stream index %d, opening\n", cinfo.codec->name, cinfo.stream_idx);
|
| 754 |
|
| 755 |
if (avcodec_open2 (cinfo.context, cinfo.codec, NULL) < 0) |
| 756 |
goto error_exit;
|
| 757 |
|
| 758 |
codec_opened = true;
|
| 759 |
|
| 760 |
/* JWT: IF abUSER ALSO WANTS TO PLAY VIDEO THEN WE SET UP POP-UP VIDEO SCREEN: */
|
| 761 |
if (play_video)
|
| 762 |
{
|
| 763 |
int vx, vy, vw, vh;
|
| 764 |
String video_windowtitle; |
| 765 |
int video_sws_scale;
|
| 766 |
int video_xmove = 0; |
| 767 |
|
| 768 |
vx = vy = vw = vh = 0;
|
| 769 |
video_xmove = aud_get_int ("ffaudio", "video_xmove"); |
| 770 |
/* -1:always let windowmanager place (random); 0(default):place window via
|
| 771 |
SDL_putenv() - may work with Windows?; 1:relocate window via XMoveWindow
|
| 772 |
(X-specific); 2:both (0, then 1). This is sometimes useful for multiple X
|
| 773 |
desktops where the default of placing the window via SDL_putenv will ALWAYS
|
| 774 |
place the window in the same desktop that Audacious is in. By setting to 1,
|
| 775 |
the window will be moved to the proper location relative to the current
|
| 776 |
desktop, and Audacious is treated as "sticky" by the window manager. Setting
|
| 777 |
to 2 MAY be useful IF for some reason, none of the other choices work properly.
|
| 778 |
*/
|
| 779 |
video_window_x = video_window_y = video_window_w = video_window_h = 0;
|
| 780 |
video_sws_scale = 0;
|
| 781 |
/* GET SAVED PREV. VIDEO WINDOW LOCN. AND SIZE AND TRY TO PLACE NEW WINDOW ACCORDINGLY: */
|
| 782 |
/* JWT: I ADDED THIS TO AVOID NEW VID. WINDOW RANDOMLY POPPING UP IN NEW LOCN., IE. WHEN REPEATING A VIDEO. */
|
| 783 |
video_window_x = aud_get_int ("ffaudio", "video_window_x"); |
| 784 |
video_window_y = aud_get_int ("ffaudio", "video_window_y"); |
| 785 |
video_window_w = aud_get_int ("ffaudio", "video_window_w"); |
| 786 |
video_window_h = aud_get_int ("ffaudio", "video_window_h"); |
| 787 |
/* FIXME?:*MIGHT?* NEED DIFFERENT CODE TO ADD THIS FEATURE TO WINDOWS! */
|
| 788 |
if (video_xmove >= 0 && video_xmove != 1) |
| 789 |
{
|
| 790 |
char video_windowpos[40]; |
| 791 |
sprintf (video_windowpos, "SDL_VIDEO_WINDOW_POS=%d,%d", video_window_x, video_window_y);
|
| 792 |
putenv (video_windowpos); |
| 793 |
} |
| 794 |
if (avcodec_open2 (vcinfo.context, vcinfo.codec, NULL) < 0) |
| 795 |
goto error_exit;
|
| 796 |
|
| 797 |
vcodec_opened = true;
|
| 798 |
|
| 799 |
/* NOW CALCULATE THE WIDTH, HEIGHT, & ASPECT BASED ON VIDEO'S SIZE & AND ANY USER PARAMATERS GIVEN:
|
| 800 |
IDEALLY, ONE SHOULD ONLY SET X OR Y AND LET Audacious CALCULATE THE OTHER DIMENSION,
|
| 801 |
SO THAT THE ASPECT RATIO IS MAINTAINED, THOUGH ONE CAN SPECIFY BOTH AND FORCE
|
| 802 |
THE ASPECT TO BE ADJUSTED TO FIT. IF A SINGLE ONE IS SPECIFIED AS "-1", THEN
|
| 803 |
THE NEW WINDOW WILL KEEP THE SAME VALUE FOR THAT DIMENSION AS THE PREV. WINDOW,
|
| 804 |
AND ADJUST THE OTHER DIMENTION ACCORDINGLY TO FIT THE NEW VIDEO'S ASPECT RATIO.
|
| 805 |
*/
|
| 806 |
video_aspect_ratio = vcinfo.context->height |
| 807 |
? (float)vcinfo.context->width / (float)vcinfo.context->height : 1.0; |
| 808 |
vx = aud_get_int ("ffaudio", "video_xsize"); |
| 809 |
vy = aud_get_int ("ffaudio", "video_ysize"); |
| 810 |
if (vx && !vy) /* User specified (or saved) width only, calc. height based on aspect: */ |
| 811 |
{
|
| 812 |
video_width = (vx == -1) ? (video_window_w ? video_window_w : vcinfo.context->width) : vx;
|
| 813 |
video_height = (int)((float)video_width / video_aspect_ratio); |
| 814 |
} |
| 815 |
else if (!vx && vy) /* User specified (or saved) height only, calc. height based on aspect: */ |
| 816 |
{
|
| 817 |
video_height = (vy == -1) ? (video_window_h ? video_window_h : vcinfo.context->height) : vy;
|
| 818 |
video_width = (int)((float)video_height * video_aspect_ratio); |
| 819 |
|
| 820 |
} |
| 821 |
else if (vx && vy) /* User specified fixed width and height: */ |
| 822 |
{
|
| 823 |
if (vx == -1 && vy == -1) /* Use saved settings or video's settings (SCREW THE ASPECT)! */ |
| 824 |
{
|
| 825 |
video_width = video_window_w ? video_window_w : vcinfo.context->width; |
| 826 |
video_height = video_window_h ? video_window_h : vcinfo.context->height; |
| 827 |
} |
| 828 |
else if (vx == -1) /* Use specified height & calculate new width based on aspect: */ |
| 829 |
{
|
| 830 |
video_height = vy; |
| 831 |
video_width = (int)((float)video_height * video_aspect_ratio); |
| 832 |
} |
| 833 |
else if (vy == -1) /* Use specified width & calculate new height based on aspect: */ |
| 834 |
{
|
| 835 |
video_width = vx; |
| 836 |
video_height = (int)((float)video_width / video_aspect_ratio); |
| 837 |
} |
| 838 |
else /* User specified window size (SCREW THE ASPECT)! */ |
| 839 |
{
|
| 840 |
video_width = vx; |
| 841 |
video_height = vy; |
| 842 |
} |
| 843 |
} |
| 844 |
else /* User specified nothing, use the video's desired wXh (& ignore saved settings!): */ |
| 845 |
{
|
| 846 |
video_width = vcinfo.context->width; |
| 847 |
video_height = vcinfo.context->height; |
| 848 |
} |
| 849 |
video_aspect_ratio = video_height |
| 850 |
? (float)video_width / (float)video_height : 1.0; /* Fall thru to square to avoid possibliity of "/0"! */ |
| 851 |
|
| 852 |
/* NOW "RESIZE" screen to user's wXh, if user set something: */
|
| 853 |
#ifndef __DARWIN__
|
| 854 |
screen = SDL_SetVideoMode (video_width, video_height, 0, SDL_RESIZABLE);
|
| 855 |
#else
|
| 856 |
screen = SDL_SetVideoMode (video_width, video_height, 24, SDL_RESIZABLE);
|
| 857 |
#endif
|
| 858 |
if (!screen) {
|
| 859 |
AUDERR ("SDL: could not re-set video mode - exiting\n");
|
| 860 |
goto error_exit;
|
| 861 |
} |
| 862 |
if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_TIMER))
|
| 863 |
{
|
| 864 |
AUDERR ("Could not initialize SDL - %s\n", SDL_GetError());
|
| 865 |
goto error_exit;
|
| 866 |
} |
| 867 |
/* JWT:FIXME:IF *nix THEN MOVE (POPUP) WINDOW (AGAIN) TO WHERE IT WAS LEFT LAST TIME (ONLY IF USER SAYS WE NEED THIS (ie. ALT. DESKTOP) */
|
| 868 |
#ifndef _WINDOWS
|
| 869 |
if (video_xmove > 0) |
| 870 |
{
|
| 871 |
SDL_SysWMinfo info; |
| 872 |
SDL_VERSION (&info.version); |
| 873 |
if (SDL_GetWMInfo (&info) > 0 ) { |
| 874 |
SDL_Delay(50);
|
| 875 |
XMoveWindow (info.info.x11.gfxdisplay, info.info.x11.wmwindow, video_window_x, video_window_y); |
| 876 |
} |
| 877 |
} |
| 878 |
#endif
|
| 879 |
video_windowtitle = aud_get_str ("ffaudio", "video_windowtitle"); |
| 880 |
if (video_windowtitle)
|
| 881 |
SDL_WM_SetCaption(video_windowtitle, NULL);
|
| 882 |
if (aud_get_int ("ffaudio", "video_sws_scale")) /* USER CAN CHOOSE SWS_SCALE VALUE. */ |
| 883 |
video_sws_scale = aud_get_int ("ffaudio", "video_sws_scale"); |
| 884 |
else
|
| 885 |
video_sws_scale = SWS_BICUBIC; /* default=4 (SWS_BICUBIC). */
|
| 886 |
|
| 887 |
bmp = SDL_CreateYUVOverlay( |
| 888 |
vcinfo.context->width, |
| 889 |
vcinfo.context->height, |
| 890 |
SDL_YV12_OVERLAY, |
| 891 |
screen |
| 892 |
); |
| 893 |
sws_ctx = sws_getContext ( |
| 894 |
vcinfo.context->width, |
| 895 |
vcinfo.context->height, |
| 896 |
vcinfo.context->pix_fmt, |
| 897 |
vcinfo.context->width, |
| 898 |
vcinfo.context->height, |
| 899 |
PIX_FMT_YUV420P, |
| 900 |
video_sws_scale, |
| 901 |
NULL,
|
| 902 |
NULL,
|
| 903 |
NULL
|
| 904 |
); |
| 905 |
} |
| 906 |
|
| 907 |
switch (cinfo.context->sample_fmt)
|
| 908 |
{
|
| 909 |
case AV_SAMPLE_FMT_U8: out_fmt = FMT_U8; planar = false; break; |
| 910 |
case AV_SAMPLE_FMT_S16: out_fmt = FMT_S16_NE; planar = false; break; |
| 911 |
case AV_SAMPLE_FMT_S32: out_fmt = FMT_S32_NE; planar = false; break; |
| 912 |
case AV_SAMPLE_FMT_FLT: out_fmt = FMT_FLOAT; planar = false; break; |
| 913 |
|
| 914 |
case AV_SAMPLE_FMT_U8P: out_fmt = FMT_U8; planar = true; break; |
| 915 |
case AV_SAMPLE_FMT_S16P: out_fmt = FMT_S16_NE; planar = true; break; |
| 916 |
case AV_SAMPLE_FMT_S32P: out_fmt = FMT_S32_NE; planar = true; break; |
| 917 |
case AV_SAMPLE_FMT_FLTP: out_fmt = FMT_FLOAT; planar = true; break; |
| 918 |
|
| 919 |
default:
|
| 920 |
AUDERR ("Unsupported audio format %d\n", (int) cinfo.context->sample_fmt); |
| 921 |
goto error_exit;
|
| 922 |
} |
| 923 |
|
| 924 |
/* Open audio output */
|
| 925 |
AUDDBG("opening audio output - bitrate=%d=\n", ic->bit_rate);
|
| 926 |
|
| 927 |
set_stream_bitrate (ic->bit_rate); |
| 928 |
open_audio (out_fmt, cinfo.context->sample_rate, cinfo.context->channels); |
| 929 |
|
| 930 |
errcount = 0;
|
| 931 |
|
| 932 |
int seek_value, ret, decoded, len, size, video_qsize, acount, vcount;
|
| 933 |
bool knit1perl2;
|
| 934 |
acount = 0; vcount = 0; /* JWT:LET'S COUNT PACKETS OF EACH SIZE FOR DEBUGGING. */ |
| 935 |
AVFrame * frame; |
| 936 |
AVFrame * vframe; |
| 937 |
/* JWT:video_qsize: MAX # VIDEO PACKETS TO QUEUE UP FOR INTERLACING TO SMOOTH VIDEO
|
| 938 |
PLAYBACK - GOOD RANGE IS 8-56, NOT ENOUGH=JITTERY VIDEO,
|
| 939 |
TOO MANY=AUDIO/VIDEO BECOME NOTICABLY OUT OF SYNC!
|
| 940 |
*/
|
| 941 |
video_qsize = (aud_get_int ("ffaudio", "video_qsize")) |
| 942 |
? aud_get_int ("ffaudio", "video_qsize") : 16; |
| 943 |
if (video_qsize < 1) |
| 944 |
video_qsize = 1;
|
| 945 |
|
| 946 |
pktQueue *pktQ; |
| 947 |
pktQ = createQueue (video_qsize); |
| 948 |
/*
|
| 949 |
JWT: THIS FLAG FORCES THE VIDEO QUEUE TO BE POPPED ONCE FOR EVERY *TWO* AUDIO
|
| 950 |
PACKETS - MOST VIDEOS SEEM TO HAVE A/B 2-1 RATIO OF AUDIO TO VIDEO PACKETS &
|
| 951 |
THIS PRETTY MUCH *ELIMINATED* THE VIDEO JITTER!!!
|
| 952 |
*/
|
| 953 |
knit1perl2 = true; /* ALTERNATES BETWEEN ON AND OFF EVERY OTHER AUDIO PACKET */ |
| 954 |
|
| 955 |
AUDDBG ("video queue size %d\n", video_qsize);
|
| 956 |
#if CHECK_LIBAVCODEC_VERSION (55, 45, 101, 55, 28, 1) |
| 957 |
frame = av_frame_alloc (); |
| 958 |
vframe = av_frame_alloc (); |
| 959 |
#else
|
| 960 |
frame = avcodec_alloc_frame (); |
| 961 |
vframe = avcodec_alloc_frame (); |
| 962 |
#endif
|
| 963 |
|
| 964 |
while (! check_stop ())
|
| 965 |
{
|
| 966 |
seek_value = check_seek (); |
| 967 |
|
| 968 |
if (seek_value >= 0) |
| 969 |
{
|
| 970 |
/* JWT:FIRST, FLUSH ANY VIDEO PACKETS SITTING IN THE QUEUE TO CLEAR THE QUEUE! */
|
| 971 |
QFlush (pktQ); |
| 972 |
knit1perl2 = true;
|
| 973 |
|
| 974 |
/* JWT: HAD TO CHANGE THIS FROM "AVSEEK_FLAG_ANY" TO AVSEEK_FLAG_BACKWARD
|
| 975 |
TO GET SEEK TO NOT RANDOMLY BRICK?! */
|
| 976 |
if (av_seek_frame (ic, -1, (int64_t) seek_value * AV_TIME_BASE / |
| 977 |
1000, AVSEEK_FLAG_BACKWARD) < 0) |
| 978 |
{
|
| 979 |
AUDERR ("error while seeking\n");
|
| 980 |
} else
|
| 981 |
errcount = 0;
|
| 982 |
|
| 983 |
seek_value = -1;
|
| 984 |
} |
| 985 |
|
| 986 |
AVPacket tmp; |
| 987 |
|
| 988 |
/* Read next frame (or more) of data */
|
| 989 |
if ((ret = av_read_frame (ic, &pkt)) < 0) |
| 990 |
{
|
| 991 |
if (ret == (int) AVERROR_EOF) |
| 992 |
{
|
| 993 |
AUDDBG ("eof reached\n");
|
| 994 |
break;
|
| 995 |
} |
| 996 |
else
|
| 997 |
{
|
| 998 |
if (++errcount > 4) |
| 999 |
{
|
| 1000 |
AUDERR ("av_read_frame error %d, giving up.\n", ret);
|
| 1001 |
break;
|
| 1002 |
} else
|
| 1003 |
continue;
|
| 1004 |
} |
| 1005 |
} else
|
| 1006 |
errcount = 0;
|
| 1007 |
|
| 1008 |
/* Ignore any other substreams */
|
| 1009 |
if (pkt.stream_index != cinfo.stream_idx)
|
| 1010 |
{
|
| 1011 |
if (!play_video || pkt.stream_index != vcinfo.stream_idx)
|
| 1012 |
{
|
| 1013 |
av_free_packet (&pkt); |
| 1014 |
continue;
|
| 1015 |
} |
| 1016 |
} |
| 1017 |
|
| 1018 |
/* Decode and play packet/frame */
|
| 1019 |
memcpy (&tmp, &pkt, sizeof(tmp));
|
| 1020 |
while (tmp.size > 0 && ! check_stop ()) |
| 1021 |
{
|
| 1022 |
/* Check for seek request and bail out if we have one */
|
| 1023 |
if (seek_value < 0) |
| 1024 |
seek_value = check_seek (); |
| 1025 |
|
| 1026 |
if (seek_value >= 0) |
| 1027 |
break;
|
| 1028 |
|
| 1029 |
decoded = 0;
|
| 1030 |
if (pkt.stream_index == cinfo.stream_idx) /* WE READ AN AUDIO PACKET: */ |
| 1031 |
{
|
| 1032 |
++acount; |
| 1033 |
AUDDBG("-read audio frame\n");
|
| 1034 |
/* JWT:IF PLAYING VIDEO, CHECK VIDEO QUEUE. */
|
| 1035 |
if (play_video)
|
| 1036 |
{
|
| 1037 |
if (knit1perl2)
|
| 1038 |
{
|
| 1039 |
/* CHECK VIDEO QUEUE FIRST EVERY OTHER AUDIO PACKET AND POP AND
|
| 1040 |
PROCESS ONE VIDEO PACKET (BEFORE EVERY OTHER AUDIO PACKET)
|
| 1041 |
*/
|
| 1042 |
AVPacket * pktRef; |
| 1043 |
|
| 1044 |
if (!isQueueFull (pktQ))
|
| 1045 |
knit1perl2 = false;
|
| 1046 |
if ((pktRef = QFront (pktQ)))
|
| 1047 |
{
|
| 1048 |
write_videoframe (sws_ctx, & cinfo, & vcinfo, vframe, bmp, pktRef, |
| 1049 |
video_width, video_height); |
| 1050 |
if (!Dequeue (pktQ))
|
| 1051 |
AUDDBG ("Queue is Empty\n");
|
| 1052 |
} |
| 1053 |
} |
| 1054 |
else
|
| 1055 |
knit1perl2 = true; /* JWT:WE'RE ASSUMING LIKELY NEAR 2x AUDIO TO VIDEO FRAMES: */ |
| 1056 |
} |
| 1057 |
/* NOW PROCESS THE AUDIO PACKET: */
|
| 1058 |
len = avcodec_decode_audio4 (cinfo.context, frame, & decoded, & tmp); |
| 1059 |
if (len < 0) |
| 1060 |
{
|
| 1061 |
AUDERR ("decode_audio() failed, code %d\n", len);
|
| 1062 |
break;
|
| 1063 |
} |
| 1064 |
|
| 1065 |
tmp.size -= len; |
| 1066 |
tmp.data += len; |
| 1067 |
|
| 1068 |
if (! decoded)
|
| 1069 |
/* continue; // JWT:NOT SURE WHY THIS WAS A CONTINUE INSTEAD OF A BREAK?! */
|
| 1070 |
break;
|
| 1071 |
|
| 1072 |
size = FMT_SIZEOF (out_fmt) * cinfo.context->channels * frame->nb_samples; |
| 1073 |
|
| 1074 |
AUDDBG ("+++++Process AUDIO packet\n");
|
| 1075 |
if (planar)
|
| 1076 |
{
|
| 1077 |
if (size > buf.len ())
|
| 1078 |
buf.resize (size); |
| 1079 |
|
| 1080 |
audio_interlace ((const void * *) frame->data, out_fmt, |
| 1081 |
cinfo.context->channels, buf.begin (), frame->nb_samples); |
| 1082 |
write_audio (buf.begin (), size); |
| 1083 |
} |
| 1084 |
else
|
| 1085 |
write_audio (frame->data[0], size);
|
| 1086 |
} |
| 1087 |
else /* WE READ A VIDEO PACKET: */ |
| 1088 |
{
|
| 1089 |
++vcount; |
| 1090 |
/* JWT: IF QUEUE IS FULL, PROCESS NEXT VIDEO PACKET FROM QUEUE. */
|
| 1091 |
AUDDBG ("+read video frame\n");
|
| 1092 |
if (isQueueFull (pktQ))
|
| 1093 |
{
|
| 1094 |
AVPacket * pktRef; |
| 1095 |
|
| 1096 |
if ((pktRef = QFront (pktQ)))
|
| 1097 |
{
|
| 1098 |
write_videoframe (sws_ctx, & cinfo, & vcinfo, vframe, bmp, pktRef, |
| 1099 |
video_width, video_height); |
| 1100 |
if (!Dequeue (pktQ))
|
| 1101 |
AUDERR ("Queue is Empty\n");
|
| 1102 |
} |
| 1103 |
AUDDBG ("Queue filled.\n");
|
| 1104 |
} |
| 1105 |
/* JWT: VIDEO PACKETS TAKE A NUMBER & GET IN LINE! */
|
| 1106 |
if (Enqueue (pktQ, pkt))
|
| 1107 |
{
|
| 1108 |
knit1perl2 = true;
|
| 1109 |
break;
|
| 1110 |
} |
| 1111 |
AUDERR ("Could not enqueue packet, refuses to wait in line, so shoot it!!\n");
|
| 1112 |
break;
|
| 1113 |
} |
| 1114 |
|
| 1115 |
/* JWT: NOW HANDLE VIDEO UI EVENTS SUCH AS RESIZE OR KILL SCREEN: */
|
| 1116 |
if (play_video)
|
| 1117 |
{
|
| 1118 |
SDL_Rect rect; |
| 1119 |
SDL_PollEvent (&event); |
| 1120 |
switch (event.type) {
|
| 1121 |
case SDL_QUIT:
|
| 1122 |
save_window_xy(); |
| 1123 |
SDL_Quit (); |
| 1124 |
play_video = false;
|
| 1125 |
break;
|
| 1126 |
|
| 1127 |
case SDL_VIDEORESIZE:
|
| 1128 |
/* Resize the screen. */
|
| 1129 |
float new_aspect_ratio;
|
| 1130 |
|
| 1131 |
new_aspect_ratio = event.resize.h |
| 1132 |
? (float)event.resize.w / (float)event.resize.h : 1.0; |
| 1133 |
if (new_aspect_ratio > video_aspect_ratio)
|
| 1134 |
{
|
| 1135 |
video_height = event.resize.h; |
| 1136 |
video_width = (int)(video_aspect_ratio * (float)video_height); |
| 1137 |
} |
| 1138 |
else
|
| 1139 |
{
|
| 1140 |
video_width = event.resize.w; |
| 1141 |
video_height = (int)((float)video_width / video_aspect_ratio); |
| 1142 |
} |
| 1143 |
|
| 1144 |
#ifndef __DARWIN__
|
| 1145 |
screen = SDL_SetVideoMode (video_width, video_height, 0, SDL_RESIZABLE);
|
| 1146 |
#else
|
| 1147 |
screen = SDL_SetVideoMode (video_width, video_height, 24, SDL_RESIZABLE);
|
| 1148 |
#endif
|
| 1149 |
|
| 1150 |
/* If there's an error, punt! */
|
| 1151 |
if (video_resizedelay > 0) |
| 1152 |
SDL_Delay(video_resizedelay); |
| 1153 |
if (screen == NULL) |
| 1154 |
{
|
| 1155 |
AUDERR ("Could not recreate screen after resizing!");
|
| 1156 |
break;
|
| 1157 |
} |
| 1158 |
case SDL_VIDEOEXPOSE:
|
| 1159 |
rect.x = 0;
|
| 1160 |
rect.y = 0;
|
| 1161 |
rect.w = video_width; |
| 1162 |
rect.h = video_height; |
| 1163 |
SDL_DisplayYUVOverlay (bmp, &rect); |
| 1164 |
break;
|
| 1165 |
|
| 1166 |
default:
|
| 1167 |
break;
|
| 1168 |
} |
| 1169 |
} |
| 1170 |
} |
| 1171 |
} |
| 1172 |
|
| 1173 |
error_exit:
|
| 1174 |
AUDDBG("end of playback - % audio frames, % video frames processed.", acount, vcount);
|
| 1175 |
if (play_video)
|
| 1176 |
{
|
| 1177 |
save_window_xy (); |
| 1178 |
SDL_Quit(); |
| 1179 |
} |
| 1180 |
|
| 1181 |
#if CHECK_LIBAVCODEC_VERSION (55, 45, 101, 55, 28, 1) |
| 1182 |
av_frame_free (& vframe); |
| 1183 |
av_frame_free (& frame); |
| 1184 |
#elif CHECK_LIBAVCODEC_VERSION (54, 59, 100, 54, 28, 0) |
| 1185 |
avcodec_free_frame (& vframe); |
| 1186 |
avcodec_free_frame (& frame); |
| 1187 |
#else
|
| 1188 |
av_free (vframe); |
| 1189 |
av_free (frame); |
| 1190 |
#endif
|
| 1191 |
|
| 1192 |
if (pkt.data)
|
| 1193 |
av_free_packet (&pkt); |
| 1194 |
if (vcodec_opened)
|
| 1195 |
avcodec_close(vcinfo.context); |
| 1196 |
if (codec_opened)
|
| 1197 |
avcodec_close(cinfo.context); |
| 1198 |
if (ic != nullptr) |
| 1199 |
close_input_file (ic, fromstdin); |
| 1200 |
|
| 1201 |
return ! error;
|
| 1202 |
} |
| 1203 |
|
| 1204 |
const char FFaudio::about[] = |
| 1205 |
N_("Multi-format audio decoding plugin for Audacious using\n"
|
| 1206 |
"FFmpeg multimedia framework (http://www.ffmpeg.org/)\n"
|
| 1207 |
"\n"
|
| 1208 |
"Audacious plugin by:\n"
|
| 1209 |
"William Pitcock <nenolod@nenolod.net>\n"
|
| 1210 |
"Matti Hämäläinen <ccr@tnsp.org>\n"
|
| 1211 |
"\n"
|
| 1212 |
"Video-playing capability (added 2015) by:\n"
|
| 1213 |
"Jim Turner <turnerjw784@yahoo.com>");
|
| 1214 |
|
| 1215 |
const char * const FFaudio::exts[] = { |
| 1216 |
/* musepack, SV7/SV8 */
|
| 1217 |
"mpc", "mp+", "mpp", |
| 1218 |
|
| 1219 |
/* windows media audio */
|
| 1220 |
"wma",
|
| 1221 |
|
| 1222 |
/* shorten */
|
| 1223 |
"shn",
|
| 1224 |
|
| 1225 |
/* atrac3 */
|
| 1226 |
"aa3", "oma", |
| 1227 |
|
| 1228 |
/* MPEG 2/4 AC3 */
|
| 1229 |
"ac3",
|
| 1230 |
|
| 1231 |
/* monkey's audio */
|
| 1232 |
"ape",
|
| 1233 |
|
| 1234 |
/* DTS */
|
| 1235 |
"dts",
|
| 1236 |
|
| 1237 |
/* VQF */
|
| 1238 |
"vqf",
|
| 1239 |
|
| 1240 |
/* MPEG-4 */
|
| 1241 |
"m4a", "mp4", |
| 1242 |
|
| 1243 |
/* WAV (there are some WAV formats sndfile can't handle) */
|
| 1244 |
"wav",
|
| 1245 |
|
| 1246 |
/* Handle OGG streams (FLAC/Vorbis etc.) */
|
| 1247 |
"ogg", "oga", |
| 1248 |
|
| 1249 |
/* Opus */
|
| 1250 |
"opus",
|
| 1251 |
|
| 1252 |
/* Speex */
|
| 1253 |
"spx",
|
| 1254 |
|
| 1255 |
/* True Audio */
|
| 1256 |
"tta",
|
| 1257 |
|
| 1258 |
/* AVI // JWT:ADDED */
|
| 1259 |
"avi",
|
| 1260 |
|
| 1261 |
/* FLV // JWT:ADDED */
|
| 1262 |
"flv",
|
| 1263 |
|
| 1264 |
/* end of table */
|
| 1265 |
nullptr
|
| 1266 |
}; |
| 1267 |
|
| 1268 |
const char * const FFaudio::mimes[] = {"application/ogg", nullptr}; |
