summaryrefslogtreecommitdiffstats
path: root/launcher/src/main.cpp
blob: e5509482c60f1c645255b1ce1829e8047d1ad6da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/*
 * Copyright (C) 2016, 2017 Mentor Graphics Development (Deutschland) GmbH
 * Copyright (c) 2018 TOYOTA MOTOR CORPORATION
 *
 * 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 <QGuiApplication>
#include <QCommandLineParser>
#include <QtGui/QGuiApplication>
#include <QtQml/QQmlApplicationEngine>
#include <QtQml/QQmlContext>
#include <QtQml/qqml.h>
#include <QQuickWindow>
#include <QThread>

#include <qlibwindowmanager.h>
#include "applicationlauncher.h"
#include "applicationmodel.h"
#include "appinfo.h"
#include "afm_user_daemon_proxy.h"
#include "homescreenhandler.h"
#include "hmi-debug.h"

// XXX: We want this DBus connection to be shared across the different
// QML objects, is there another way to do this, a nice way, perhaps?
org::AGL::afm::user *afm_user_daemon_proxy;

namespace {

struct Cleanup {
    static inline void cleanup(org::AGL::afm::user *p) {
        delete p;
        afm_user_daemon_proxy = Q_NULLPTR;
    }
};

}

int main(int argc, char *argv[])
{
    QString myname = QString("launcher");
    QGuiApplication a(argc, argv);

    // use launch process
    QScopedPointer<org::AGL::afm::user, Cleanup> afm_user_daemon_proxy(new org::AGL::afm::user("org.AGL.afm.user",
                                                                                               "/org/AGL/afm/user",
                                                                                               QDBusConnection::sessionBus(),
                                                                                               0));
    ::afm_user_daemon_proxy = afm_user_daemon_proxy.data();

    QCoreApplication::setOrganizationDomain("LinuxFoundation");
    QCoreApplication::setOrganizationName("AutomotiveGradeLinux");
    QCoreApplication::setApplicationName(myname);
    QCoreApplication::setApplicationVersion("0.1.0");

    QCommandLineParser parser;
    parser.addPositionalArgument("port", a.translate("main", "port for binding"));
    parser.addPositionalArgument("secret", a.translate("main", "secret for binding"));
    parser.addHelpOption();
    parser.addVersionOption();
    parser.process(a);
    QStringList positionalArguments = parser.positionalArguments();

    int port = 1700;
    QString token = "wm";

    if (positionalArguments.length() == 2) {
        port = positionalArguments.takeFirst().toInt();
        token = positionalArguments.takeFirst();
    }

    HMI_DEBUG("launcher","port = %d, token = %s", port, token.toStdString().c_str());

    // import C++ class to QML
    qmlRegisterType<ApplicationModel>("AppModel", 1, 0, "ApplicationModel");

    // DBus
    qDBusRegisterMetaType<AppInfo>();
    qDBusRegisterMetaType<QList<AppInfo> >();

    ApplicationLauncher *launcher = new ApplicationLauncher();
    QLibWindowmanager* layoutHandler = new QLibWindowmanager();
    if(layoutHandler->init(port,token) != 0){
        exit(EXIT_FAILURE);
    }

    AGLScreenInfo screenInfo(layoutHandler->get_scale_factor());

    if (layoutHandler->requestSurface(myname) != 0) {
        exit(EXIT_FAILURE);
    }

    layoutHandler->set_event_handler(QLibWindowmanager::Event_SyncDraw, [layoutHandler, myname](json_object *object) {
        layoutHandler->endDraw(myname);
    });

    layoutHandler->set_event_handler(QLibWindowmanager::Event_Visible, [layoutHandler, launcher](json_object *object) {
        QString label = QString(json_object_get_string(	json_object_object_get(object, "drawing_name") ));
        qDebug() << label;
        QMetaObject::invokeMethod(launcher, "setCurrent", Qt::QueuedConnection, Q_ARG(QString, label == "HomeScreen" ? "Home" : label));
    });

    layoutHandler->set_event_handler(QLibWindowmanager::Event_Invisible, [layoutHandler, launcher](json_object *object) {
        const char* label = json_object_get_string(	json_object_object_get(object, "drawing_name") );
        HMI_DEBUG("launch", "surface %s Event_Invisible", label);
    });

    HomescreenHandler* homescreenHandler = new HomescreenHandler();
    homescreenHandler->init(port, token.toStdString().c_str(), layoutHandler, myname);

    QUrl bindingAddress;
    bindingAddress.setScheme(QStringLiteral("ws"));
    bindingAddress.setHost(QStringLiteral("localhost"));
    bindingAddress.setPort(port);
    bindingAddress.setPath(QStringLiteral("/api"));

    QUrlQuery query;
    query.addQueryItem(QStringLiteral("token"), token);
    bindingAddress.setQuery(query);

    const QByteArray hack_delay = qgetenv("HMI_LAUNCHER_STARTUP_DELAY");
    int delay_time = 1;

    if (!hack_delay.isEmpty()) {
       delay_time = (QString::fromLocal8Bit(hack_delay)).toInt();
    }

    QThread::sleep(delay_time);
    qDebug("Sleep %d sec to resolve race condtion between HomeScreen and Launcher", delay_time);

    // mail.qml loading
    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty(QStringLiteral("layoutHandler"), layoutHandler);
    engine.rootContext()->setContextProperty(QStringLiteral("homescreenHandler"), homescreenHandler);
    engine.rootContext()->setContextProperty(QStringLiteral("launcher"), launcher);
    engine.rootContext()->setContextProperty(QStringLiteral("screenInfo"), &screenInfo);
    engine.load(QUrl(QStringLiteral("qrc:/Launcher.qml")));
    homescreenHandler->getRunnables();

    QObject *root = engine.rootObjects().first();
    QQuickWindow *window = qobject_cast<QQuickWindow *>(root);
    QObject::connect(window, SIGNAL(frameSwapped()), layoutHandler, SLOT(slotActivateSurface()));

    return a.exec();
}
case SND_PCM_FORMAT_S16_LE: case SND_PCM_FORMAT_S16_BE: pcmSampleSize = 2; break; case SND_PCM_FORMAT_U24_LE: case SND_PCM_FORMAT_U24_BE: case SND_PCM_FORMAT_S24_LE: case SND_PCM_FORMAT_S24_BE: pcmSampleSize = 3; break; case SND_PCM_FORMAT_U32_LE: case SND_PCM_FORMAT_U32_BE: case SND_PCM_FORMAT_S32_LE: case SND_PCM_FORMAT_S32_BE: pcmSampleSize = 4; break; default: pcmSampleSize = 0; } return pcmSampleSize; } PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { int error; snd_pcm_hw_params_t *pxmHwParams; snd_pcm_sw_params_t *pxmSwParams; snd_pcm_format_t format; snd_pcm_access_t access; AlsaPcmHwInfoT * opts = pcm->params; const char * modeS = mode==SND_PCM_STREAM_PLAYBACK?"PLAYBACK":"CAPTURE"; AFB_ApiInfo(mixer->api, "%s: mixer info %s uid %s , pcm %s, mode %s", __func__, mixer->info, mixer->uid, pcm->cid.cardid, modeS); // retrieve hardware config from PCM snd_pcm_hw_params_alloca(&pxmHwParams); error = snd_pcm_hw_params_any(pcm->handle, pxmHwParams); if (error < 0) { AFB_ApiError(mixer->api, "%s: Failed to get parameters: %s", __func__, snd_strerror(error)); goto OnErrorExit; } AFB_ApiDebug(mixer->api, "PARAMS before:\n"); AlsaDumpPcmParams(mixer, pxmHwParams); if (!opts->access) opts->access = SND_PCM_ACCESS_RW_INTERLEAVED; snd_pcm_hw_params_get_access(pxmHwParams, &access); error = snd_pcm_hw_params_set_access(pcm->handle, pxmHwParams, opts->access); if (error) { AFB_ApiError(mixer->api, "%s set_access failed (ignore this error): mixer=%s cardid=%s access=%d Fail current=%d mode error=%s", __func__, mixer->uid, pcm->cid.cardid, opts->access, access, snd_strerror(error)); //Fulup goto OnErrorExit; }; if (opts->format != SND_PCM_FORMAT_UNKNOWN) { snd_pcm_hw_params_get_format(pxmHwParams, &format); if ((error = snd_pcm_hw_params_set_format(pcm->handle, pxmHwParams, opts->format)) < 0) { AFB_ApiError(mixer->api, "%s: mixer=%s cardid=%s Set_Format=%s (%d) FAILED current=%d error=%s", __func__, mixer->uid, pcm->cid.cardid, opts->formatS, opts->format, format, snd_strerror(error)); AlsaDumpFormats(mixer, pcm->handle); goto OnErrorExit; } } if (opts->rate > 0 ) { AFB_ApiInfo(mixer->api," %s: set rate to %d", __func__, opts->rate); unsigned int pcmRate = opts->rate; /* Attempt to set the rate. Failing on a capture dev is acceptable */ error = snd_pcm_hw_params_set_rate_near(pcm->handle, pxmHwParams, &opts->rate, 0); if ( mode == SND_PCM_STREAM_PLAYBACK && error < 0) { AFB_ApiError(mixer->api, "%s: mixer=%s cardid=%s FailSet_Rate=%d error=%s", __func__, mixer->uid, pcm->cid.cardid, opts->rate, snd_strerror(error)); goto OnErrorExit; } // check we got requested rate if (mode == SND_PCM_STREAM_PLAYBACK && opts->rate != pcmRate) { AFB_ApiError(mixer->api, "%s: mixer=%s cardid=%s Set_Rate Fail ask=%dHz get=%dHz", __func__, mixer->uid, pcm->cid.cardid,pcmRate, opts->rate); goto OnErrorExit; } } if (opts->channels) { if ((error = snd_pcm_hw_params_set_channels(pcm->handle, pxmHwParams, opts->channels)) < 0) { AFB_ApiError(mixer->api, "%s: mixer=%s cardid=%s Set_Channels=%d Fail error=%s", __func__, mixer->uid, pcm->cid.cardid, opts->channels, snd_strerror(error)); goto OnErrorExit; }; } /* The following code, that * 1) sets period time/size; buffer time/size hardware params * 2) sets start and stop threashold in software params * ... is taken as such from 'aplay' in alsa-utils */ unsigned buffer_time = 0; unsigned period_time = 0; snd_pcm_uframes_t buffer_frames = 0; snd_pcm_uframes_t period_frames = 0; error = snd_pcm_hw_params_get_buffer_time_max(pxmHwParams, &buffer_time, 0); printf("HW_BUFFER_TIME MAX is %d\n", buffer_time); if (buffer_time > 500000) buffer_time = 500000; if (period_time == 0 && period_frames == 0) { if (buffer_time > 0) period_time = buffer_time / 4; else period_frames = buffer_frames / 4; } if (period_time > 0) { printf("SET PERIOD TIME to %d\n", period_time); error = snd_pcm_hw_params_set_period_time_near(pcm->handle, pxmHwParams, &period_time, 0); } else { printf("SET PERIOD SIZE\n"); error = snd_pcm_hw_params_set_period_size_near(pcm->handle, pxmHwParams, &period_frames, 0); } if (error < 0) { AFB_ApiError(mixer->api, "%s: mixer=%s cardid=%s Fail to set period in hwparams error=%s", __func__, mixer->uid, pcm->cid.cardid, snd_strerror(error)); goto OnErrorExit; } if (buffer_time > 0) { printf("SET BUFFER TIME to %d\n", buffer_time); error = snd_pcm_hw_params_set_buffer_time_near(pcm->handle, pxmHwParams, &buffer_time, 0); } else { printf("SET BUFFER SIZE\n"); error = snd_pcm_hw_params_set_buffer_size_near(pcm->handle, pxmHwParams, &buffer_frames); } if (error < 0) { AFB_ApiError(mixer->api, "%s: mixer=%s cardid=%s Fail to set buffer in hwparams error=%s", __func__, mixer->uid, pcm->cid.cardid, snd_strerror(error)); goto OnErrorExit; } // store selected values if ((error = snd_pcm_hw_params(pcm->handle, pxmHwParams)) < 0) { AFB_ApiError(mixer->api, "%s: mixer=%s cardid=%s Fail to apply hwparams error=%s", __func__, mixer->uid, pcm->cid.cardid, snd_strerror(error)); goto OnErrorExit; } AFB_ApiDebug(mixer->api, "PARAMS after:\n"); AlsaDumpPcmParams(mixer, pxmHwParams); // check we effective hw params after optional format change snd_pcm_hw_params_get_channels(pxmHwParams, &opts->channels); snd_pcm_hw_params_get_format(pxmHwParams, &opts->format); snd_pcm_hw_params_get_rate(pxmHwParams, &opts->rate, 0); AFB_ApiInfo(mixer->api, "rate is %d", opts->rate); opts->sampleSize = AlsaPeriodSize(opts->format); if (opts->sampleSize == 0) { AFB_ApiError(mixer->api, "%s: mixer=%s cardid=%s Fail unsupported format format=%d", __func__, mixer->uid, pcm->cid.cardid, opts->format); goto OnErrorExit; } snd_pcm_uframes_t chunk_size, buffer_size; snd_pcm_hw_params_get_period_size(pxmHwParams, &chunk_size, 0); snd_pcm_hw_params_get_buffer_size(pxmHwParams, &buffer_size); if (chunk_size == buffer_size) { AFB_ApiError(mixer->api, "Can't use period equal to buffer size (%lu == %lu)", chunk_size, buffer_size); goto OnErrorExit; } int avail_min = -1; size_t n; int rate = opts->rate; if (avail_min < 0) n = chunk_size; else n = (size_t) ((double)rate * avail_min / 1000000); // retrieve software config from PCM snd_pcm_sw_params_alloca(&pxmSwParams); snd_pcm_sw_params_current(pcm->handle, pxmSwParams); // available_min is the minimum number of frames available to read, // when the call to poll returns. Assume this is the same for playback (not checked that) if ((error = snd_pcm_sw_params_set_avail_min(pcm->handle, pxmSwParams, n)) < 0) { AFB_ApiError(mixer->api, "%s: mixer=%s cardid=%s Fail set_buffersize error=%s", __func__, mixer->uid, pcm->cid.cardid, snd_strerror(error)); goto OnErrorExit; }; snd_pcm_sw_params_get_avail_min(pxmSwParams, &pcm->avail_min); int start_delay = 0; snd_pcm_uframes_t start_threshold; /* round up to closest transfer boundary */ n = buffer_size; if (start_delay <= 0) { start_threshold = n + (size_t)((double)rate * start_delay / 1000000); } else start_threshold = (size_t)((double)rate * start_delay / 1000000); if (start_threshold < 1) start_threshold = 1; if (start_threshold > n/2) start_threshold = n/2; printf("CALCULATED START THRESHOLD: %ld\n", start_threshold); if (mode == SND_PCM_STREAM_PLAYBACK) { start_threshold = 1; } AFB_ApiInfo(mixer->api, "%s: Set start threshold to %ld", modeS, start_threshold); error = snd_pcm_sw_params_set_start_threshold(pcm->handle, pxmSwParams, start_threshold); if (error < 0) { AFB_ApiError(mixer->api, "%s: mixer=%s cardid=%s failed set start_threshold, error=%s", __func__, mixer->uid, pcm->cid.cardid, snd_strerror(error)); goto OnErrorExit; } // push software params into PCM if ((error = snd_pcm_sw_params(pcm->handle, pxmSwParams)) < 0) { AFB_ApiError(mixer->api, "%s: mixer=%s cardid=%s Fail to push SW params error=%s", __func__, mixer->uid, pcm->cid.cardid, snd_strerror(error)); goto OnErrorExit; }; AFB_ApiNotice(mixer->api, "%s: mixer=%s cardid=%s Done channels=%d rate=%d format=%d access=%d ... done !", __func__, mixer->uid, pcm->cid.cardid, opts->channels, opts->rate, opts->format, opts->access); return 0; OnErrorExit: return -1; } STATIC int AlsaPcmReadCB( struct pollfd * pfd, AlsaPcmCopyHandleT * pcmCopyHandle) { char string[32]; snd_pcm_sframes_t availIn; snd_pcm_t * pcmIn = pcmCopyHandle->pcmIn->handle; alsa_ringbuf_t * rbuf = pcmCopyHandle->rbuf; snd_pcm_uframes_t bufSize = alsa_ringbuf_buffer_size(rbuf); int err; // PCM has was closed if ((pfd->revents & POLLHUP) != 0) { AFB_ApiNotice(pcmCopyHandle->api, "%s PCM=%s hanghup/disconnected", __func__, ALSA_PCM_UID(pcmIn, string)); goto ExitOnSuccess; } // ignore any non input events. This is not supposed to happen ever if ((pfd->revents & EPOLLIN) == 0) { goto ExitOnSuccess; } // do we have waiting frames ? availIn = snd_pcm_avail_update(pcmIn); if (availIn <= 0) { if (availIn == -EPIPE) { int ret = xrun(pcmIn, (int)availIn); printf("XXX read EPIPE (recov=%d) !\n", ret); // For some (undocumented...) reason, a start is mandatory. snd_pcm_start(pcmIn); } goto ExitOnSuccess; } pthread_mutex_lock(&pcmCopyHandle->mutex); snd_pcm_sframes_t availInBuf = alsa_ringbuf_frames_free(rbuf); pthread_mutex_unlock(&pcmCopyHandle->mutex); /* we get too much data, take what we can now, * hopefully we will have more luck next time */ if (availIn > availInBuf) { // printf("INCOMING BUFFER TOO SMALL !\n"); availIn = availInBuf; } while (true) { pthread_mutex_lock(&pcmCopyHandle->mutex); snd_pcm_sframes_t r = alsa_ringbuf_frames_free(rbuf); if (r <= 0) { pthread_mutex_unlock(&pcmCopyHandle->mutex); // Wake up the reader, in case it is sleeping, // that lets it an opportunity to pop something. sem_post(&pcmCopyHandle->sem); break; } if (r < availIn) r = availIn; char buf[r*pcmCopyHandle->frame_size]; pthread_mutex_unlock(&pcmCopyHandle->mutex); r = snd_pcm_readi(pcmIn, buf, r); if (r == 0) { break; } if (r < 0) { if (r == -EPIPE) { err = xrun(pcmIn, (int)r); printf("read EPIPE (%d), recov %d\n", ++pcmCopyHandle->read_err_count, err); goto ExitOnSuccess; } else if (r == -ESTRPIPE) { printf("read ESTRPIPE\n"); if ((err = suspend(pcmIn, (int)r)) < 0) goto ExitOnSuccess; r = 0; } else { goto ExitOnSuccess; } } pthread_mutex_lock(&pcmCopyHandle->mutex); alsa_ringbuf_frames_push(rbuf, buf, r); snd_pcm_uframes_t used = alsa_ringbuf_frames_used(rbuf); pthread_mutex_unlock(&pcmCopyHandle->mutex); // Wait for having the buffer full enough before waking up the playback // else it will starve immediately. if (used > 0.8 * (double)bufSize) { sem_post(&pcmCopyHandle->sem); } availIn -= r; // completed, we have read everything if (availIn <= 0) { break; } } ExitOnSuccess: return 0; } static int xrun( snd_pcm_t * pcm, int error) { int err; if ((err = snd_pcm_recover(pcm, error, 1)) < 0) { return err; } return 0; } static int suspend( snd_pcm_t * pcm, int error) { int err; while ((err = snd_pcm_resume(pcm)) == -EAGAIN) { usleep(1); } if (err < 0) return xrun(pcm, error); return 0; } static void readSuspend(AlsaPcmCopyHandleT * pcmCopyHandle) { // will be deaf pcmCopyHandle->saveFd = pcmCopyHandle->pollFds[0].fd; pcmCopyHandle->pollFds[0].fd = -1; AFB_ApiNotice(pcmCopyHandle->api, "capture muted"); } static void readResume(AlsaPcmCopyHandleT * pcmCopyHandle) { // undeaf it pcmCopyHandle->pollFds[0].fd = pcmCopyHandle->saveFd; AFB_ApiNotice(pcmCopyHandle->api, "capture unmuted"); } static void *readThreadEntry(void *handle) { #define LOOP_TIMEOUT_MSEC 10*1000 /* 10 seconds */ AlsaPcmCopyHandleT *pcmCopyHandle = (AlsaPcmCopyHandleT*) handle; pcmCopyHandle->tid = (int) syscall(SYS_gettid); AFB_ApiNotice(pcmCopyHandle->api, "%s :%s/%d Started, muted=%d", __func__, pcmCopyHandle->info, pcmCopyHandle->tid, pcmCopyHandle->pcmIn->mute); struct pollfd * mutePfd = &pcmCopyHandle->pollFds[0]; struct pollfd * framePfd = &pcmCopyHandle->pollFds[1]; mutePfd->events = POLLIN | POLLHUP; framePfd->events = POLLIN | POLLHUP; bool muted = pcmCopyHandle->pcmIn->mute; if (muted) readSuspend(pcmCopyHandle); /* loop until end */ for (;;) { int err = poll(pcmCopyHandle->pollFds, pcmCopyHandle->nbPcmFds, LOOP_TIMEOUT_MSEC); if (err < 0) { AFB_ApiError(pcmCopyHandle->api, "%s: poll err %s", __func__, strerror(errno)); continue; } if (err == 0) { /* timeout */ AFB_ApiDebug(pcmCopyHandle->api, "%s(%s) alive", __func__, pcmCopyHandle->pcmIn->cid.cardid ); continue; } // handle the un/mute order if ((mutePfd->revents & EPOLLIN) != 0) { bool mute; size_t ret = read(mutePfd->fd, &mute, sizeof(mute)); if (ret <= 0) continue; if (mute == muted) continue; muted = mute; if (muted) { readSuspend(pcmCopyHandle); } else { readResume(pcmCopyHandle); } continue; } unsigned short revents; int ret = snd_pcm_poll_descriptors_revents(pcmCopyHandle->pcmIn->handle, &pcmCopyHandle->pollFds[1], 1, &revents); if (ret == -ENODEV) { sleep(1); continue; } if (framePfd->revents & POLLHUP) { AFB_ApiNotice(pcmCopyHandle->api, "Frame POLLHUP"); continue; } AlsaPcmReadCB(&pcmCopyHandle->pollFds[1], pcmCopyHandle); } pthread_exit(0); return NULL; } static void *writeThreadEntry(void *handle) { AlsaPcmCopyHandleT *pcmCopyHandle = (AlsaPcmCopyHandleT*) handle; snd_pcm_t * pcmOut = pcmCopyHandle->pcmOut->handle; alsa_ringbuf_t * rbuf = pcmCopyHandle->rbuf; const snd_pcm_sframes_t threshold = 1000; for (;;) { sem_wait(&pcmCopyHandle->sem); while (true) { snd_pcm_sframes_t r; snd_pcm_sframes_t availOut = snd_pcm_avail(pcmOut); if (availOut < 0) { if (availOut == -EPIPE) { printf("write update EPIPE\n"); xrun(pcmOut, (int)availOut); continue; } if (availOut == -ESTRPIPE) { printf("write update ESTRPIPE\n"); suspend(pcmOut, (int)availOut); continue; } } // no space for output if (availOut <= threshold) { usleep(500); continue; } pthread_mutex_lock(&pcmCopyHandle->mutex); r = alsa_ringbuf_frames_used(rbuf); if (r <= 0) { pthread_mutex_unlock(&pcmCopyHandle->mutex); break; // will wait again } if (r > availOut) r = availOut; char buf[r*pcmCopyHandle->frame_size]; alsa_ringbuf_frames_pop(rbuf, buf, r); pthread_mutex_unlock(&pcmCopyHandle->mutex); r = snd_pcm_writei( pcmOut, buf, r); if (r <= 0) { if (r == -EPIPE) { int err = xrun(pcmOut, (int)r); printf("XXX write EPIPE (%d), recov %d\n", ++pcmCopyHandle->write_err_count , err); continue; } else if (r == -ESTRPIPE) { printf("XXX write ESTRPIPE\n"); break; } printf("Unhandled error %s\n", strerror(errno)); break; } } } pthread_exit(0); return NULL; } PUBLIC int AlsaPcmCopyMuteSignal(SoftMixerT *mixer, AlsaPcmCtlT *pcmIn, bool mute) { ssize_t ret = write(pcmIn->muteFd, &mute, sizeof(mute)); (void) ret; return 0; } PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts) { char string[32]; int error; // Fulup need to check https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___direct.html AlsaDumpPcmInfo(mixer,"PcmIn",pcmIn->handle); AlsaDumpPcmInfo(mixer,"PcmOut",pcmOut->handle); AFB_ApiInfo(mixer->api, "%s: Configure CAPTURE PCM", __func__); /* remember configuration of capture */ pcmIn->params = (AlsaPcmHwInfoT*)malloc(sizeof(AlsaPcmHwInfoT)); memcpy(pcmIn->params, opts, sizeof(AlsaPcmHwInfoT)); pcmOut->params = (AlsaPcmHwInfoT*)malloc(sizeof(AlsaPcmHwInfoT)); memcpy(pcmOut->params, opts, sizeof(AlsaPcmHwInfoT)); pcmIn->mixer = mixer; pcmOut->mixer = mixer; AFB_ApiInfo(mixer->api, "%s: Configure CAPTURE PCM", __func__); // prepare PCM for capture and replay error = AlsaPcmConf(mixer, pcmIn, SND_PCM_STREAM_CAPTURE); if (error) { AFB_ApiError(mixer->api, "%s: PCM configuration for capture failed", __func__); goto OnErrorExit; } AFB_ApiInfo(mixer->api, "%s: Configure PLAYBACK PCM", __func__); // input and output should match error = AlsaPcmConf(mixer, pcmOut, SND_PCM_STREAM_PLAYBACK); if (error) { AFB_ApiError(mixer->api, "%s: PCM configuration for playback failed", __func__); goto OnErrorExit; } // Prepare PCM for usage if ((error = snd_pcm_prepare(pcmOut->handle)) < 0) { AFB_ApiError(mixer->api, "%s: Fail to prepare PLAYBACK PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); goto OnErrorExit; }; // Prepare PCM for usage if ((error = snd_pcm_prepare(pcmIn->handle)) < 0) { AFB_ApiError(mixer->api, "%s: Fail to prepare CAPTURE PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); goto OnErrorExit; }; // Start PCM if ((error = snd_pcm_start(pcmOut->handle)) < 0) { AFB_ApiError(mixer->api, "%s: Fail to start PLAYBACK PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); goto OnErrorExit; }; // Start PCM if ((error = snd_pcm_start(pcmIn->handle)) < 0) { AFB_ApiError(mixer->api, "%s: Fail to start CAPTURE PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); goto OnErrorExit; }; AlsaPcmCopyHandleT *cHandle= calloc(1, sizeof(AlsaPcmCopyHandleT)); cHandle->info = "pcmCpy"; cHandle->pcmIn = pcmIn; cHandle->pcmOut = pcmOut; cHandle->api = mixer->api; cHandle->channels = opts->channels; cHandle->frame_size = (snd_pcm_format_physical_width(opts->format) / 8) * opts->channels; AFB_ApiInfo(mixer->api, "%s: Frame size is %zu", __func__, cHandle->frame_size); snd_pcm_uframes_t nbFrames = 2 * opts->rate; // Exactly 2 second of buffer cHandle->rbuf = alsa_ringbuf_new(nbFrames, cHandle->frame_size); cHandle->read_err_count = 0; cHandle->write_err_count = 0; AFB_ApiInfo(mixer->api, "%s Copy buffer nbframes is %zu", __func__, nbFrames); // get FD poll descriptor for capture PCM int pcmInCount = snd_pcm_poll_descriptors_count(pcmIn->handle); if (pcmInCount <= 0) { AFB_ApiError(mixer->api, "%s: Fail pcmIn=%s get fds count error=%s", __func__, ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); goto OnErrorExit; }; if (pcmInCount > 1) { AFB_ApiError(mixer->api, "%s: Fail, pcmIn=%s; having more than one FD on capture PCM is not supported (here, %d)", __func__, ALSA_PCM_UID(pcmOut->handle, string) , pcmInCount); goto OnErrorExit; } struct pollfd pcmInFd; if ((error = snd_pcm_poll_descriptors(pcmIn->handle, &pcmInFd, 1)) < 0) { AFB_ApiError(mixer->api, "%s: Fail pcmIn=%s get pollfds error=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); goto OnErrorExit; }; // create the mute pipe int pMuteFd[2]; error = pipe(pMuteFd); if (error < 0) { AFB_ApiError(mixer->api, "Unable to create the mute signaling pipe\n"); goto OnErrorExit; } struct pollfd mutePFd; // read end mutePFd.fd = pMuteFd[0]; mutePFd.events = POLLIN; mutePFd.revents = 0; // write end pcmIn->muteFd = pMuteFd[1]; cHandle->pollFds[0] = mutePFd; cHandle->pollFds[1] = pcmInFd; cHandle->nbPcmFds = pcmInCount+1; error = sem_init(&cHandle->sem, 0 , 0); if (error < 0) { AFB_ApiError(mixer->api, "%s Fail initialize loop semaphore pcmIn=%s err=%d", __func__, ALSA_PCM_UID(pcmIn->handle, string), error); goto OnErrorExit; } pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); error = pthread_mutex_init(&cHandle->mutex, &attr); if (error < 0) { AFB_ApiError(mixer->api, "%s Fail initialize loop mutex pcmIn=%s err=%d", __func__, ALSA_PCM_UID(pcmIn->handle, string), error); } /// start a thread for writing if ((error = pthread_create(&cHandle->wthread, NULL, &readThreadEntry, cHandle)) < 0) { AFB_ApiError(mixer->api, "%s Fail create write thread pcmOut=%s err=%d", __func__, ALSA_PCM_UID(pcmOut->handle, string), error); goto OnErrorExit; } // start a thread for reading if ((error = pthread_create(&cHandle->rthread, NULL, &writeThreadEntry, cHandle)) < 0) { AFB_ApiError(mixer->api, "%s Fail create read thread pcmIn=%s err=%d", __func__, ALSA_PCM_UID(pcmIn->handle, string), error); goto OnErrorExit; } // request a higher priority for each audio stream thread struct sched_param params; params.sched_priority = sched_get_priority_max(SCHED_FIFO); error= pthread_setschedparam(cHandle->rthread, SCHED_FIFO, &params); if (error) { AFB_ApiWarning(mixer->api, "%s: Failed to increase stream read thread priority pcmIn=%s err=%s", __func__, ALSA_PCM_UID(pcmIn->handle, string), strerror(error)); } error= pthread_setschedparam(cHandle->wthread, SCHED_FIFO, &params); if (error) { AFB_ApiWarning(mixer->api, "%s: Failed to increase stream write thread priority pcmIn=%s err=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), strerror(error)); } return 0; OnErrorExit: AFB_ApiError(mixer->api, "%s: - pcmIn=%s" , __func__, ALSA_PCM_UID(pcmIn->handle, string)); AFB_ApiError(mixer->api, "%s: - pcmOut=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string)); return -1; }