Get printvideo audio working on Windows and MacOS

This commit is contained in:
Justine Tunney 2024-09-06 06:45:27 -07:00
parent 07fde68d52
commit 5d3b91d8b9
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
18 changed files with 93470 additions and 212 deletions

View file

@ -16,6 +16,7 @@ TOOL_VIZ_BINS = \
$(TOOL_VIZ_COMS:%=%.dbg)
TOOL_VIZ_DIRECTDEPS = \
DSP_AUDIO \
DSP_CORE \
DSP_MPEG \
DSP_SCALE \

View file

@ -16,6 +16,7 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "dsp/audio/cosmoaudio/cosmoaudio.h"
#include "dsp/core/core.h"
#include "dsp/core/half.h"
#include "dsp/core/illumination.h"
@ -142,8 +143,6 @@ Effects Shortcuts:\n\
CTRL-G {Unsharp,Sharp}\n\
\n\
Environment Variables:\n\
SOX overrides location of SoX executable\n\
FFPLAY overrides location of FFmpeg ffplay executable\n\
ROWS=𝑦 sets height [inarticulate mode]\n\
COLUMNS=𝑥 sets width [inarticulate mode]\n\
TERM=dumb inarticulate mode\n\
@ -158,11 +157,6 @@ in a different format, then it's fast and easy to convert them:\n\
The terminal fonts we recommend are PragmataPro, Bitstream Vera Sans\n\
Mono (known as DejaVu Sans Mono in the open source community), Menlo,\n\
and Lucida Console.\n\
\n\
On Linux, playing audio requires either `sox` or `ffplay` being on\n\
the $PATH. Kitty is the fastest terminal. Alacritty also has a fast\n\
display. GNOME Terminal and xterm both work well in 256-color or ANSI\n\
mode.\n\
\n"
#define CTRL(C) ((C) ^ 0100)
@ -226,16 +220,6 @@ struct FrameBuffer {
struct FrameBufferVirtualScreenInfo vscreen;
};
static const struct itimerval kTimerDisarm = {
{0, 0},
{0, 0},
};
static const struct itimerval kTimerHalfSecondSingleShot = {
{0, 0},
{0, 500000},
};
static const struct NamedVector kPrimaries[] = {
{"BT.601", &kBt601Primaries},
{"BT.709", &kBt709Primaries},
@ -256,6 +240,7 @@ static const struct NamedVector kLightings[] = {
static plm_t *plm_;
static float gamma_;
static int volscale_;
struct CosmoAudio *ca_;
static enum Blur blur_;
static enum Sharp sharp_;
static jmp_buf jb_, jbi_;
@ -263,32 +248,27 @@ static double pary_, parx_;
static struct TtyIdent ti_;
static struct YCbCr *ycbcr_;
static bool emboss_, sobel_;
static volatile int playpid_;
static const char *patharg_;
static struct winsize wsize_;
static float hue_, sat_, lit_;
static volatile bool resized_;
static void *xtcodes_, *audio_;
static struct FrameBuffer fb0_;
static unsigned chans_, srate_;
static volatile bool ignoresigs_;
static size_t dh_, dw_, framecount_;
static struct FrameCountRing fcring_;
static volatile bool resized_, piped_;
static int lumakernel_, chromakernel_;
static openspeaker_f tryspeakerfns_[4];
static int primaries_, lighting_, swing_;
static uint64_t t1, t2, t3, t4, t5, t6, t8;
static const char *sox_, *ffplay_, *patharg_;
static int homerow_, lastrow_, infd_, outfd_;
static struct VtFrame vtframe_[2], *f1_, *f2_;
static struct Graphic graphic_[2], *g1_, *g2_;
static struct timespec deadline_, dura_, starttime_;
static bool yes_, stats_, dither_, ttymode_, istango_;
static struct timespec decode_start_, f1_start_, f2_start_;
static int16_t pcm_[PLM_AUDIO_SAMPLES_PER_FRAME * 2 / 8][8];
static int16_t pcmscale_[PLM_AUDIO_SAMPLES_PER_FRAME * 2 / 8][8];
static bool fullclear_, historyclear_, tuned_, yonly_, gotvideo_;
static int homerow_, lastrow_, playfd_, infd_, outfd_, speakerfails_;
static char status_[7][200], logpath_[PATH_MAX], fifopath_[PATH_MAX],
chansstr_[32], sratestr_[32];
static char status_[7][200], logpath_[PATH_MAX], chansstr_[32], sratestr_[32];
static void OnCtrlC(void) {
longjmp(jb_, 1);
@ -298,18 +278,6 @@ static void OnResize(void) {
resized_ = true;
}
static void OnSigPipe(void) {
piped_ = true;
}
static void OnSigChld(void) {
playpid_ = 0, piped_ = true;
}
static void StrikeDownCrapware(int sig) {
kill(playpid_, SIGKILL);
}
static struct timespec GetGraceTime(void) {
return timespec_sub(deadline_, timespec_real());
}
@ -349,32 +317,9 @@ static int GetLighting(const char *s) {
return GetNamedVector(kLightings, ARRAYLEN(kLightings), s);
}
static bool CloseSpeaker(void) {
int rc, wstatus;
rc = 0;
pthread_yield();
if (playfd_) {
rc |= close(playfd_);
playfd_ = -1;
}
if (playpid_) {
kill(playpid_, SIGTERM);
xsigaction(SIGALRM, StrikeDownCrapware, SA_RESETHAND, 0, 0);
setitimer(ITIMER_REAL, &kTimerHalfSecondSingleShot, NULL);
while (playpid_) {
if (waitpid(playpid_, &wstatus, 0) != -1) {
rc |= WEXITSTATUS(wstatus);
} else if (errno == EINTR) {
continue;
} else {
rc = -1;
}
break;
}
playpid_ = 0;
setitimer(ITIMER_REAL, &kTimerDisarm, NULL);
}
return !!rc;
static void CloseSpeaker(void) {
if (ca_)
cosmoaudio_close(ca_);
}
static void ResizeVtFrame(struct VtFrame *f, size_t yn, size_t xn) {
@ -473,106 +418,14 @@ static void DimensionDisplay(void) {
} while (resized_);
}
static int WriteAudio(int fd, const void *data, size_t size, int deadlinems) {
ssize_t rc;
const char *p;
size_t wrote, n;
p = data;
n = size;
do {
TryAgain:
if ((rc = write(fd, p, n)) != -1) {
wrote = rc;
p += wrote;
n -= wrote;
} else if (errno == EINTR) {
goto TryAgain;
} else if (errno == EAGAIN) {
if (poll((struct pollfd[]){{fd, POLLOUT}}, 1, deadlinems) == 0) {
return etimedout();
}
} else {
return -1;
}
} while (n);
return 0;
}
static bool TrySpeaker(const char *prog, char *const *args) {
int pipefds[2];
CHECK_NE(-1, pipe2(pipefds, O_CLOEXEC));
if (!(playpid_ = fork())) {
dup2(pipefds[0], 0);
dup2(fileno(__log_file), 1);
dup2(fileno(__log_file), 2);
close(fileno(__log_file));
execv(prog, args);
abort();
}
playfd_ = pipefds[1];
return true;
}
static bool TrySox(void) {
return TrySpeaker(sox_, ARGZ("play", "-q", "-c", chansstr_, "-traw",
"-esigned", "-b16", "-r", sratestr_, "-"));
}
static bool TryFfplay(void) {
return TrySpeaker(ffplay_, ARGZ("ffplay", "-nodisp", "-loglevel", "quiet",
"-fflags", "nobuffer", "-ac", chansstr_,
"-ar", sratestr_, "-f", "s16le", "pipe:"));
}
static bool OpenSpeaker(void) {
size_t i;
static bool once, count;
if (!once) {
once = true;
i = 0;
if (ffplay_)
tryspeakerfns_[i++] = TryFfplay;
if (sox_)
tryspeakerfns_[i++] = TrySox;
}
snprintf(fifopath_, sizeof(fifopath_), "%s%s.%d.%d.wav", __get_tmpdir(),
firstnonnull(program_invocation_short_name, "unknown"), getpid(),
count);
for (i = 0; i < ARRAYLEN(tryspeakerfns_); ++i) {
if (tryspeakerfns_[i]) {
if (++speakerfails_ <= 2 && tryspeakerfns_[i]()) {
return true;
} else {
speakerfails_ = 0;
tryspeakerfns_[i] = NULL;
}
}
}
return false;
return cosmoaudio_open(&ca_, srate_, chans_) == COSMOAUDIO_SUCCESS;
}
static void OnAudio(plm_t *mpeg, plm_samples_t *samples, void *user) {
if (playfd_ != -1) {
DEBUGF("OnAudio() [grace=%,ldns]", timespec_tonanos(GetGraceTime()));
CHECK_EQ(2, chans_);
CHECK_EQ(ARRAYLEN(pcm_) * 8, samples->count * chans_);
float2short(ARRAYLEN(pcm_), pcm_, (void *)samples->interleaved);
scalevolume(ARRAYLEN(pcm_), pcm_, volscale_);
sad16x8n(ARRAYLEN(pcm_), pcm_, pcmscale_);
DEBUGF("transcoded audio");
TryAgain:
if (WriteAudio(playfd_, pcm_, sizeof(pcm_), 1000) != -1) {
DEBUGF("WriteAudio(%d, %zu) ok [grace=%,ldns]", playfd_,
samples->count * 2, timespec_tonanos(GetGraceTime()));
} else {
WARNF("WriteAudio(%d, %zu) failed: %s", playfd_, samples->count * 2,
strerror(errno));
CloseSpeaker();
if (OpenSpeaker()) {
goto TryAgain;
}
}
}
if (!ca_)
return;
cosmoaudio_write(ca_, samples->interleaved, samples->count);
}
static void DescribeAlgorithms(char *p) {
@ -885,7 +738,6 @@ static void OnVideo(plm_t *mpeg, plm_frame_t *pf, void *user) {
static void OpenVideo(void) {
size_t yn, xn;
playfd_ = -1;
INFOF("%s(%`'s)", "OpenVideo", patharg_);
CHECK_NOTNULL((plm_ = plm_create_with_filename(patharg_)));
swing_ = 219;
@ -1336,14 +1188,8 @@ static void RestoreTty(void) {
}
static void HandleSignals(void) {
if (piped_) {
WARNF("SIGPIPE");
CloseSpeaker();
piped_ = false;
}
if (resized_) {
if (resized_)
RefreshDisplay();
}
}
static void PrintVideo(void) {
@ -1393,17 +1239,6 @@ static bool AskUserYesOrNoQuestion(const char *prompt) {
return c == 'y' || c == 'Y';
}
static bool CanPlayAudio(void) {
if (ffplay_ || sox_) {
return true;
} else if (AskUserYesOrNoQuestion(
"ffplay not found; continue without audio?")) {
return false;
} else {
longjmp(jb_, 1);
}
}
static void PrintUsage(int rc, int fd) {
tinyprint(fd, "Usage: ", program_invocation_name, USAGE, NULL);
exit(rc);
@ -1442,8 +1277,6 @@ static void GetOpts(int argc, char *argv[]) {
}
static void OnExit(void) {
if (playpid_)
kill(playpid_, SIGTERM), sched_yield();
if (plm_)
plm_destroy(plm_), plm_ = NULL;
YCbCrFree(&ycbcr_);
@ -1460,11 +1293,6 @@ static void OnExit(void) {
CloseSpeaker();
}
static void MakeLatencyLittleLessBad(void) {
LOGIFNEG1(sys_mlockall(MCL_CURRENT));
LOGIFNEG1(nice(-5));
}
static void PickDefaults(void) {
/*
* Direct color ain't true color -- it just means xterm does the
@ -1478,13 +1306,6 @@ static void PickDefaults(void) {
}
}
static void RenounceSpecialPrivileges(void) {
if (issetugid()) {
setegid(getgid());
seteuid(getuid());
}
}
#define FBIOGET_VSCREENINFO 0x4600
#define FBIOGET_FSCREENINFO 0x4602
@ -1582,7 +1403,6 @@ static void TryToOpenFrameBuffer(void) {
int main(int argc, char *argv[]) {
sigset_t wut;
const char *s;
ShowCrashReports();
gamma_ = 2.4;
volscale_ -= 2;
@ -1599,17 +1419,6 @@ int main(int argc, char *argv[]) {
if (optind == argc)
PrintUsage(EX_USAGE, STDERR_FILENO);
patharg_ = argv[optind];
s = commandvenv("SOX", "sox");
sox_ = s ? strdup(s) : 0;
s = commandvenv("FFPLAY", "ffplay");
ffplay_ = s ? strdup(s) : 0;
if (!sox_ && !ffplay_) {
fprintf(stderr, "please install either the "
"`play` (sox) or "
"`ffplay` (ffmpeg) "
"commands, so printvideo can play audio\n");
usleep(10000);
}
infd_ = STDIN_FILENO;
outfd_ = STDOUT_FILENO;
if (!setjmp(jb_)) {
@ -1617,8 +1426,6 @@ int main(int argc, char *argv[]) {
xsigaction(SIGHUP, OnCtrlC, 0, 0, NULL);
xsigaction(SIGTERM, OnCtrlC, 0, 0, NULL);
xsigaction(SIGWINCH, OnResize, 0, 0, NULL);
xsigaction(SIGCHLD, OnSigChld, 0, 0, NULL);
xsigaction(SIGPIPE, OnSigPipe, 0, 0, NULL);
if (ttyraw(kTtyLfToCrLf) != -1)
ttymode_ = true;
__cxa_atexit((void *)OnExit, NULL, NULL);
@ -1629,10 +1436,7 @@ int main(int argc, char *argv[]) {
infd_ = -1;
}
/* CHECK_NE(-1, fcntl(outfd_, F_SETFL, O_NONBLOCK)); */
if (CanPlayAudio())
MakeLatencyLittleLessBad();
TryToOpenFrameBuffer();
RenounceSpecialPrivileges();
if (t2 > t1)
longjmp(jb_, 1);
OpenVideo();