Merge branch 'master' into pr6360

Manual changes done:
  * Merged the interface-changes under the already master'd changes.
  * Moved the hwdec-related option changes to video/decode/vd_lavc.c.
This commit is contained in:
Jan Ekström 2019-03-11 01:00:27 +02:00
commit 199aabddcc
32 changed files with 580 additions and 315 deletions

View File

@ -47,6 +47,12 @@ Interface changes
- support for `--spirv-compiler=nvidia` has been removed, leaving `shaderc`
as the only option. The `--spirv-compiler` option itself has been marked
as deprecated, and may be removed in the future.
- split up `--tone-mapping-desaturate`` into strength + exponent, instead of
only using a single value (which previously just controlled the exponent).
The strength now linearly blends between the linear and nonlinear tone
mapped versions of a color.
- add --hdr-peak-decay-rate and --hdr-scene-threshold-low/high
- add --tone-mapping-max-boost
- ipc: require that "request_id" fields are integers. Other types are still
accepted for compatibility, but this will stop in the future. Also, if no
request_id is provided, 0 will be assumed.

View File

@ -1817,7 +1817,9 @@ Property list
are the xrandr names (LVDS1, HDMI1, DP1, VGA1, etc.). On Windows, these
are the GDI names (\\.\DISPLAY1, \\.\DISPLAY2, etc.) and the first display
in the list will be the one that Windows considers associated with the
window (as determined by the MonitorFromWindow API.)
window (as determined by the MonitorFromWindow API.) On macOS these are the
Display Product Names as used in the System Information and only one display
name is returned since a window can only be on one screen.
``display-fps`` (RW)
The refresh rate of the current display. Currently, this is the lowest FPS

View File

@ -1092,7 +1092,7 @@ Video
You can get the list of allowed codecs with ``mpv --vd=help``. Remove the
prefix, e.g. instead of ``lavc:h264`` use ``h264``.
By default, this is set to ``h264,vc1,wmv3,hevc,mpeg2video,vp9``. Note that
By default, this is set to ``h264,vc1,hevc,vp9``. Note that
the hardware acceleration special codecs like ``h264_vdpau`` are not
relevant anymore, and in fact have been removed from Libav in this form.
@ -1326,8 +1326,10 @@ Audio
Since mpv 0.18.1, this always controls the internal mixer (aka "softvol").
``--replaygain=<no|track|album>``
Adjust volume gain according to the track-gain or album-gain replaygain
value stored in the file metadata (default: no replaygain).
Adjust volume gain according to replaygain values stored in the file
metadata. With ``--replaygain=no`` (the default), perform no adjustment.
With ``--replaygain=track``, apply track gain. With ``--replaygain=album``,
apply album gain if present and fall back to track gain otherwise.
``--replaygain-preamp=<db>``
Pre-amplification gain in dB to apply to the selected replaygain gain
@ -3988,8 +3990,8 @@ Network
DVB
---
``--dvbin-card=<1-4>``
Specifies using card number 1-4 (default: 1).
``--dvbin-card=<0-15>``
Specifies using card number 0-15 (default: 0).
``--dvbin-file=<filename>``
Instructs mpv to read the channels list from ``<filename>``. The default is
@ -5073,7 +5075,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
The user should independently guarantee this before using these signal
formats for display.
``--target-peak=<nits>``
``--target-peak=<auto|nits>``
Specifies the measured peak brightness of the output display, in cd/m^2
(AKA nits). The interpretation of this brightness depends on the configured
``--target-trc``. In all cases, it imposes a limit on the signal values
@ -5085,9 +5087,9 @@ The following video options are currently all specific to ``--vo=gpu`` and
above 100 essentially causes the display to be treated as if it were an HDR
display in disguise. (See the note below)
By default, the chosen peak defaults to an appropriate value based on the
TRC in use. For SDR curves, it defaults to 100. For HDR curves, it
defaults to 100 * the transfer function's nominal peak.
In ``auto`` mode (the default), the chosen peak is an appropriate value
based on the TRC in use. For SDR curves, it uses 100. For HDR curves, it
uses 100 * the transfer function's nominal peak.
.. note::
@ -5164,6 +5166,14 @@ The following video options are currently all specific to ``--vo=gpu`` and
linear
Specifies the scale factor to use while stretching. Defaults to 1.0.
``--tone-mapping-max-boost=<1.0..10.0>``
Upper limit for how much the tone mapping algorithm is allowed to boost
the average brightness by over-exposing the image. The default value of 1.0
allows no additional brightness boost. A value of 2.0 would allow
over-exposing by a factor of 2, and so on. Raising this setting can help
reveal details that would otherwise be hidden in dark scenes, but raising
it too high will make dark scenes appear unnaturally bright.
``--hdr-compute-peak=<auto|yes|no>``
Compute the HDR peak and frame average brightness per-frame instead of
relying on tagged metadata. These values are averaged over local regions as
@ -5174,17 +5184,50 @@ The following video options are currently all specific to ``--vo=gpu`` and
The special value ``auto`` (default) will enable HDR peak computation
automatically if compute shaders and SSBOs are supported.
``--tone-mapping-desaturate=<value>``
Apply desaturation for highlights. The parameter essentially controls the
steepness of the desaturation curve. The higher the parameter, the more
aggressively colors will be desaturated. This setting helps prevent
unnaturally blown-out colors for super-highlights, by (smoothly) turning
into white instead. This makes images feel more natural, at the cost of
reducing information about out-of-range colors.
``--hdr-peak-decay-rate=<1.0..1000.0>``
The decay rate used for the HDR peak detection algorithm (default: 100.0).
This is only relevant when ``--hdr-compute-peak`` is enabled. Higher values
make the peak decay more slowly, leading to more stable values at the cost
of more "eye adaptation"-like effects (although this is mitigated somewhat
by ``--hdr-scene-threshold``). A value of 1.0 (the lowest possible) disables
all averaging, meaning each frame's value is used directly as measured,
but doing this is not recommended for "noisy" sources since it may lead
to excessive flicker. (In signal theory terms, this controls the time
constant "tau" of an IIR low pass filter)
The default of 0.5 provides a good balance. This value is weaker than the
ACES ODT curves' recommendation, but works better for most content in
practice. A setting of 0.0 disables this option.
``--hdr-scene-threshold-low=<0.0..100.0>``, ``--hdr-scene-threshold-high=<0.0..100.0>``
The lower and upper thresholds (in dB) for a brightness difference
to be considered a scene change (default: 5.5 low, 10.0 high). This is only
relevant when ``--hdr-compute-peak`` is enabled. Normally, small
fluctuations in the frame brightness are compensated for by the peak
averaging mechanism, but for large jumps in the brightness this can result
in the frame remaining too bright or too dark for up to several seconds,
depending on the value of ``--hdr-peak-decay-rate``. To counteract this,
when the brightness between the running average and the current frame
exceeds the low threshold, mpv will make the averaging filter more
aggressive, up to the limit of the high threshold (at which point the
filter becomes instant).
``--tone-mapping-desaturate=<0.0..1.0>``
Apply desaturation for highlights (default: 0.75). The parameter controls
the strength of the desaturation curve. A value of 0.0 completely disables
it, while a value of 1.0 means that overly bright colors will tend towards
white. (This is not always the case, especially not for highlights that are
near primary colors)
Values in between apply progressively more/less aggressive desaturation.
This setting helps prevent unnaturally oversaturated colors for
super-highlights, by (smoothly) turning them into less saturated (per
channel tone mapped) colors instead. This makes images feel more natural,
at the cost of chromatic distortions for out-of-range colors. The default
value of 0.75 provides a good balance. Setting this to 0.0 preserves the
chromatic accuracy of the tone mapping process.
``--tone-mapping-desaturate-exponent=<0.0..20.0>``
This setting controls the exponent of the desaturation curve, which
controls how bright a color needs to be in order to start being
desaturated. The default of 1.5 provides a reasonable balance. Decreasing
this exponent makes the curve more aggressive.
``--gamut-warning``
If enabled, mpv will mark all clipped/out-of-gamut pixels that exceed a
@ -5245,12 +5288,14 @@ The following video options are currently all specific to ``--vo=gpu`` and
Size of the 3D LUT generated from the ICC profile in each dimension.
Default is 64x64x64. Sizes may range from 2 to 512.
``--icc-contrast=<0-1000000>``
``--icc-contrast=<0-1000000|inf>``
Specifies an upper limit on the target device's contrast ratio. This is
detected automatically from the profile if possible, but for some profiles
it might be missing, causing the contrast to be assumed as infinite. As a
result, video may appear darker than intended. This only affects BT.1886
content. The default of 0 means no limit.
content. The default of 0 means no limit if the detected contrast is less
than 100000, and limits to 1000 otherwise. Use ``--icc-contrast=inf`` to
preserve the infinite contrast (most likely when using OLED displays).
``--blend-subtitles=<yes|video|no>``
Blend subtitles directly onto upscaled video frames, before interpolation

View File

@ -1715,8 +1715,6 @@ static void prune_old_packets(struct demux_internal *in)
}
prev = prev->next;
}
update_seek_ranges(range);
}
bool done = false;
@ -1725,6 +1723,8 @@ static void prune_old_packets(struct demux_internal *in)
remove_head_packet(queue);
}
update_seek_ranges(range);
if (range != in->current_range && range->seek_start == MP_NOPTS_VALUE)
free_empty_cached_ranges(in);
}
@ -2100,12 +2100,17 @@ static struct replaygain_data *decode_rgain(struct mp_log *log,
{
struct replaygain_data rg = {0};
// Set values in *rg, using track gain as a fallback for album gain if the
// latter is not present. This behavior matches that in demux/demux_lavf.c's
// export_replaygain; if you change this, please make equivalent changes
// there too.
if (decode_gain(log, tags, "REPLAYGAIN_TRACK_GAIN", &rg.track_gain) >= 0 &&
decode_peak(log, tags, "REPLAYGAIN_TRACK_PEAK", &rg.track_peak) >= 0)
{
if (decode_gain(log, tags, "REPLAYGAIN_ALBUM_GAIN", &rg.album_gain) < 0 ||
decode_peak(log, tags, "REPLAYGAIN_ALBUM_PEAK", &rg.album_peak) < 0)
{
// Album gain is undefined; fall back to track gain.
rg.album_gain = rg.track_gain;
rg.album_peak = rg.track_peak;
}

View File

@ -80,8 +80,10 @@ static struct tl_parts *parse_edl(bstr str)
{
struct tl_parts *tl = talloc_zero(NULL, struct tl_parts);
while (str.len) {
if (bstr_eatstart0(&str, "#"))
if (bstr_eatstart0(&str, "#")) {
bstr_split_tok(str, "\n", &(bstr){0}, &str);
continue;
}
if (bstr_eatstart0(&str, "\n") || bstr_eatstart0(&str, ";"))
continue;
bool is_header = bstr_eatstart0(&str, "!");

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2004 Michael Niedermayer <michaelni@gmx.at>
* Copyright (C) 2018 Google LLC
*
* This file is part of mpv.
*
@ -588,17 +589,27 @@ static void export_replaygain(demuxer_t *demuxer, struct sh_stream *sh,
av_rgain = (AVReplayGain*)src_sd->data;
rgain = talloc_ptrtype(demuxer, rgain);
rgain->track_gain = (av_rgain->track_gain != INT32_MIN) ?
av_rgain->track_gain / 100000.0f : 0.0;
// Set values in *rgain, using track gain as a fallback for album gain
// if the latter is not present. This behavior matches that in
// demux/demux.c's decode_rgain; if you change this, please make
// equivalent changes there too.
if (av_rgain->track_gain != INT32_MIN && av_rgain->track_peak != 0.0) {
// Track gain is defined.
rgain->track_gain = av_rgain->track_gain / 100000.0f;
rgain->track_peak = av_rgain->track_peak / 100000.0f;
rgain->track_peak = (av_rgain->track_peak != 0.0) ?
av_rgain->track_peak / 100000.0f : 1.0;
rgain->album_gain = (av_rgain->album_gain != INT32_MIN) ?
av_rgain->album_gain / 100000.0f : 0.0;
rgain->album_peak = (av_rgain->album_peak != 0.0) ?
av_rgain->album_peak / 100000.0f : 1.0;
if (av_rgain->album_gain != INT32_MIN &&
av_rgain->album_peak != 0.0)
{
// Album gain is also defined.
rgain->album_gain = av_rgain->album_gain / 100000.0f;
rgain->album_peak = av_rgain->album_peak / 100000.0f;
} else {
// Album gain is undefined; fall back to track gain.
rgain->album_gain = rgain->track_gain;
rgain->album_peak = rgain->track_peak;
}
}
// This must be run only before the stream was added, otherwise there
// will be race conditions with accesses from the user thread.

View File

@ -399,7 +399,7 @@ static bool reorder_planes(struct mp_aframe *mpa, int *reorder,
if (!mp_aframe_set_chmap(mpa, newmap))
return false;
int num_planes = newmap->num;
int num_planes = mp_aframe_get_planes(mpa);
uint8_t **planes = mp_aframe_get_data_rw(mpa);
uint8_t *old_planes[MP_NUM_CHANNELS];
assert(num_planes <= MP_NUM_CHANNELS);

View File

@ -49,3 +49,11 @@ static int SWIFT_KEY_MOUSE_LEAVE = MP_KEY_MOUSE_LEAVE;
static int SWIFT_KEY_MOUSE_ENTER = MP_KEY_MOUSE_ENTER;
static int SWIFT_KEY_STATE_DOWN = MP_KEY_STATE_DOWN;
static int SWIFT_KEY_STATE_UP = MP_KEY_STATE_UP;
// only used from Swift files and therefore seen as unused by the c compiler
static void SWIFT_TARRAY_STRING_APPEND(void *t, char ***a, int *i, char *s) __attribute__ ((unused));
static void SWIFT_TARRAY_STRING_APPEND(void *t, char ***a, int *i, char *s)
{
MP_TARRAY_APPEND(t, *a, *i, s);
}

View File

@ -0,0 +1,58 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
import Cocoa
extension NSScreen {
public var displayID: CGDirectDisplayID {
get {
return deviceDescription["NSScreenNumber"] as! CGDirectDisplayID
}
}
public var displayName: String? {
get {
var name: String? = nil
var object: io_object_t
var iter = io_iterator_t()
let matching = IOServiceMatching("IODisplayConnect")
let result = IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iter)
if result != KERN_SUCCESS || iter == 0 { return nil }
repeat {
object = IOIteratorNext(iter)
let info = IODisplayCreateInfoDictionary(object, IOOptionBits(kIODisplayOnlyPreferredName)).takeRetainedValue() as! [String:AnyObject]
if (info[kDisplayVendorID] as? UInt32 == CGDisplayVendorNumber(displayID) &&
info[kDisplayProductID] as? UInt32 == CGDisplayModelNumber(displayID) &&
info[kDisplaySerialNumber] as? UInt32 ?? 0 == CGDisplaySerialNumber(displayID))
{
if let productNames = info["DisplayProductName"] as? [String:String],
let productName = productNames.first?.value
{
name = productName
break
}
}
} while object != 0
IOObjectRelease(iter)
return name
}
}
}

View File

@ -55,7 +55,7 @@ static const NSEventModifierFlags NSEventModifierFlagOption = NSAlternateKeyMask
#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9)
typedef NSUInteger NSModalResponse;
static const NSModalResponse NSModalResponseOK = NSFileHandlingPanelOKButton
static const NSModalResponse NSModalResponseOK = NSFileHandlingPanelOKButton;
#endif
#endif

View File

@ -4052,6 +4052,7 @@ static bool is_property_set(int action, void *val)
case M_PROPERTY_SWITCH:
case M_PROPERTY_SET_STRING:
case M_PROPERTY_SET_NODE:
case M_PROPERTY_MULTIPLY:
return true;
case M_PROPERTY_KEY_ACTION: {
struct m_property_action_arg *key = val;

View File

@ -752,7 +752,7 @@ int mp_add_external_file(struct MPContext *mpctx, char *filename,
if (!demuxer)
goto err_out;
if (opts->rebase_start_time)
if (filter != STREAM_SUB && opts->rebase_start_time)
demux_set_ts_offset(demuxer, -demuxer->start_time);
bool has_any = false;

View File

@ -476,12 +476,9 @@ function mp.dispatch_events(allow_wait)
while mp.keep_running do
local wait = 0
if not more_events then
wait = process_timers()
if wait == nil then
for _, handler in ipairs(idle_handlers) do
handler()
end
wait = 1e20 -- infinity for all practical purposes
wait = process_timers() or 1e20 -- infinity for all practical purposes
for _, handler in ipairs(idle_handlers) do
handler()
end
-- Resume playloop - important especially if an error happened while
-- suspended, and the error was handled, but no resume was done.

View File

@ -430,7 +430,8 @@ local function add_file(s)
append_property(s, "media-title", {prefix="Title:"})
end
append_property(s, "file-format", {prefix="Format/Protocol:"})
local fs = append_property(s, "file-size", {prefix="Size:"})
append_property(s, "file-format", {prefix="Format/Protocol:", nl=fs and "" or o.nl})
local ch_index = mp.get_property_number("chapter")
if ch_index and ch_index >= 0 then
@ -457,7 +458,6 @@ local function add_file(s)
indent=o.prefix_sep, no_prefix_markup=true})
end
end
append_property(s, "file-size", {prefix="Size:"})
end
@ -536,9 +536,9 @@ local function add_audio(s)
append(s, "", {prefix=o.nl .. o.nl .. "Audio:", nl="", indent=""})
append_property(s, "audio-codec", {prefix_sep="", nl="", indent=""})
append(s, r["format"], {prefix="Format:"})
local cc = append(s, r["channel-count"], {prefix="Channels:"})
append(s, r["format"], {prefix="Format:", nl=cc and "" or o.nl})
append(s, r["samplerate"], {prefix="Sample Rate:", suffix=" Hz"})
append(s, r["channel-count"], {prefix="Channels:"})
append_property(s, "packet-audio-bitrate", {prefix="Bitrate:", suffix=" kbps"})
append_filters(s, "af", "Filters:")
end

View File

@ -73,7 +73,7 @@ static pthread_mutex_t global_dvb_state_lock = PTHREAD_MUTEX_INITIALIZER;
const struct m_sub_options stream_dvb_conf = {
.opts = (const m_option_t[]) {
OPT_STRING("prog", cfg_prog, 0),
OPT_INTRANGE("card", cfg_devno, 0, 1, 4),
OPT_INTRANGE("card", cfg_devno, 0, 0, MAX_ADAPTERS-1),
OPT_INTRANGE("timeout", cfg_timeout, 0, 1, 30),
OPT_STRING("file", cfg_file, M_OPT_FILE),
OPT_FLAG("full-transponder", cfg_full_transponder, 0),
@ -1157,7 +1157,7 @@ dvb_state_t *dvb_get_state(stream_t *stream)
if (devno.len) {
bstr r;
priv->cfg_devno = bstrtoll(devno, &r, 0);
if (r.len || priv->cfg_devno < 0 || priv->cfg_devno > MAX_ADAPTERS) {
if (r.len || priv->cfg_devno < 0 || priv->cfg_devno >= MAX_ADAPTERS) {
MP_ERR(stream, "invalid devno: '%.*s'\n", BSTR_P(devno));
return NULL;
}

View File

@ -406,8 +406,11 @@ char *filter_SDH(struct sd *sd, char *format, int n_ignored, char *data, int len
line_with_text = true;
}
} else if (*rp && rp[0] != '\\') {
if (rp[0] > 32 && rp[0] < 127 && rp[0] != '-')
if ((rp[0] > 32 && rp[0] < 127 && rp[0] != '-') ||
(unsigned char)rp[0] >= 0xC0)
{
line_with_text = true;
}
append(sd, buf, rp[0]);
rp++;
} else if (rp[0] == '\\' && rp[1] != 'N') {

View File

@ -140,13 +140,13 @@ const struct m_sub_options vd_lavc_conf = {
.framedrop = AVDISCARD_NONREF,
.dr = 1,
.hwdec_api = HAVE_RPI ? "mmal" : "no",
.hwdec_codecs = "h264,vc1,wmv3,hevc,mpeg2video,vp9",
.hwdec_codecs = "h264,vc1,hevc,vp9",
},
};
struct hwdec_info {
char name[64];
char method_name[16]; // non-unique name describing the hwdec method
char method_name[24]; // non-unique name describing the hwdec method
const AVCodec *codec; // implemented by this codec
enum AVHWDeviceType lavc_device; // if not NONE, get a hwdevice
bool copying; // if true, outputs sw frames, or copy to sw ourselves

View File

@ -75,7 +75,8 @@ class EventsView: NSView {
return true
}
} else if types.contains(NSURLPboardType) {
if let url = pb.propertyList(forType: NSURLPboardType) as? [Any] {
if var url = pb.propertyList(forType: NSURLPboardType) as? [String] {
url = url.filter{ !$0.isEmpty }
EventsResponder.sharedInstance().handleFilesArray(url)
return true
}

View File

@ -300,6 +300,7 @@ class Window: NSWindow, NSWindowDelegate {
let intermediateFrame = aspectFit(rect: newFrame, in: screen!.frame)
cocoaCB.view.layerContentsPlacement = .scaleProportionallyToFill
hideTitleBar()
styleMask.remove(.fullScreen)
setFrame(intermediateFrame, display: true)
NSAnimationContext.runAnimationGroup({ (context) -> Void in
@ -435,9 +436,7 @@ class Window: NSWindow, NSWindowDelegate {
}
override func setFrame(_ frameRect: NSRect, display flag: Bool) {
let newFrame = !isAnimating && isInFullscreen ? targetScreen!.frame :
frameRect
super.setFrame(newFrame, display: flag)
super.setFrame(frameRect, display: flag)
if keepAspect {
contentAspectRatio = unfsContentFrame!.size

View File

@ -148,10 +148,9 @@ class CocoaCB: NSObject {
func startDisplayLink(_ vo: UnsafeMutablePointer<vo>) {
let opts: mp_vo_opts = vo.pointee.opts.pointee
let screen = getScreenBy(id: Int(opts.screen_id)) ?? NSScreen.main()
let displayId = screen!.deviceDescription["NSScreenNumber"] as! UInt32
CVDisplayLinkCreateWithActiveCGDisplays(&link)
CVDisplayLinkSetCurrentCGDisplay(link!, displayId)
CVDisplayLinkSetCurrentCGDisplay(link!, screen!.displayID)
if #available(macOS 10.12, *) {
CVDisplayLinkSetOutputHandler(link!) { link, now, out, inFlags, outFlags -> CVReturn in
self.mpv.reportRenderFlip()
@ -170,8 +169,7 @@ class CocoaCB: NSObject {
}
func updateDisplaylink() {
let displayId = UInt32(window.screen!.deviceDescription["NSScreenNumber"] as! Int)
CVDisplayLinkSetCurrentCGDisplay(link!, displayId)
CVDisplayLinkSetCurrentCGDisplay(link!, window.screen!.displayID)
queue.asyncAfter(deadline: DispatchTime.now() + 0.1) {
self.flagEvents(VO_EVENT_WIN_STATE)
@ -302,9 +300,8 @@ class CocoaCB: NSObject {
var reconfigureCallback: CGDisplayReconfigurationCallBack = { (display, flags, userInfo) in
if flags.contains(.setModeFlag) {
let ccb: CocoaCB = MPVHelper.bridge(ptr: userInfo!)
let displayID = (ccb.window.screen!.deviceDescription["NSScreenNumber"] as! NSNumber).intValue
if UInt32(displayID) == display {
ccb.mpv.sendVerbose("Detected display mode change, updating screen refresh rate\n");
if ccb.window.screen!.displayID == display {
ccb.mpv.sendVerbose("Detected display mode change, updating screen refresh rate");
ccb.flagEvents(VO_EVENT_WIN_STATE)
}
}
@ -423,6 +420,20 @@ class CocoaCB: NSObject {
let minimized = data!.assumingMemoryBound(to: Int32.self)
minimized.pointee = ccb.window.isMiniaturized ? VO_WIN_STATE_MINIMIZED : Int32(0)
return VO_TRUE
case VOCTRL_GET_DISPLAY_NAMES:
let opts: mp_vo_opts = vo!.pointee.opts!.pointee
let dnames = data!.assumingMemoryBound(to: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?.self)
var array: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>? = nil
var count: Int32 = 0
let screen = ccb.window != nil ? ccb.window.screen :
ccb.getScreenBy(id: Int(opts.screen_id)) ??
NSScreen.main()
let displayName = screen?.displayName ?? "Unknown"
SWIFT_TARRAY_STRING_APPEND(nil, &array, &count, ta_xstrdup(nil, displayName))
SWIFT_TARRAY_STRING_APPEND(nil, &array, &count, nil)
dnames.pointee = array
return VO_TRUE
case VOCTRL_UPDATE_WINDOW_TITLE:
let titleData = data!.assumingMemoryBound(to: Int8.self)
let title = String(cString: titleData)

View File

@ -83,7 +83,7 @@ const struct m_sub_options mp_icc_conf = {
OPT_FLAG("icc-profile-auto", profile_auto, 0),
OPT_STRING("icc-cache-dir", cache_dir, M_OPT_FILE),
OPT_INT("icc-intent", intent, 0),
OPT_INTRANGE("icc-contrast", contrast, 0, 0, 1000000),
OPT_CHOICE_OR_INT("icc-contrast", contrast, 0, 0, 1000000, ({"inf", -1})),
OPT_STRING_VALIDATE("icc-3dlut-size", size_str, 0, validate_3dlut_size_opt),
OPT_REPLACED("3dlut-size", "icc-3dlut-size"),

View File

@ -16,6 +16,7 @@
*/
#include <assert.h>
#include <math.h>
#include "common/msg.h"
#include "misc/ctype.h"
@ -52,9 +53,11 @@ static bool parse_rpn_szexpr(struct bstr line, struct szexp out[MAX_SZEXP_SIZE])
case '-': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_SUB; continue;
case '*': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_MUL; continue;
case '/': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_DIV; continue;
case '%': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_MOD; continue;
case '!': exp->tag = SZEXP_OP1; exp->val.op = SZEXP_OP_NOT; continue;
case '>': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_GT; continue;
case '<': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_LT; continue;
case '=': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_EQ; continue;
}
if (mp_isdigit(word.start[0])) {
@ -118,8 +121,10 @@ bool eval_szexpr(struct mp_log *log, void *priv,
case SZEXP_OP_SUB: res = op1 - op2; break;
case SZEXP_OP_MUL: res = op1 * op2; break;
case SZEXP_OP_DIV: res = op1 / op2; break;
case SZEXP_OP_MOD: res = fmodf(op1, op2); break;
case SZEXP_OP_GT: res = op1 > op2; break;
case SZEXP_OP_LT: res = op1 < op2; break;
case SZEXP_OP_EQ: res = op1 == op2; break;
default: abort();
}

View File

@ -30,9 +30,11 @@ enum szexp_op {
SZEXP_OP_SUB,
SZEXP_OP_MUL,
SZEXP_OP_DIV,
SZEXP_OP_MOD,
SZEXP_OP_NOT,
SZEXP_OP_GT,
SZEXP_OP_LT,
SZEXP_OP_EQ,
};
enum szexp_tag {

View File

@ -313,9 +313,16 @@ static const struct gl_video_opts gl_video_opts_def = {
.alpha_mode = ALPHA_BLEND_TILES,
.background = {0, 0, 0, 255},
.gamma = 1.0f,
.tone_mapping = TONE_MAPPING_HABLE,
.tone_mapping_param = NAN,
.tone_mapping_desat = 0.5,
.tone_map = {
.curve = TONE_MAPPING_HABLE,
.curve_param = NAN,
.max_boost = 1.0,
.decay_rate = 100.0,
.scene_threshold_low = 5.5,
.scene_threshold_high = 10.0,
.desat = 0.75,
.desat_exp = 1.5,
},
.early_flush = -1,
.hwdec_interop = "auto",
};
@ -351,21 +358,30 @@ const struct m_sub_options gl_video_conf = {
OPT_FLAG("gamma-auto", gamma_auto, 0),
OPT_CHOICE_C("target-prim", target_prim, 0, mp_csp_prim_names),
OPT_CHOICE_C("target-trc", target_trc, 0, mp_csp_trc_names),
OPT_INTRANGE("target-peak", target_peak, 0, 10, 10000),
OPT_CHOICE("tone-mapping", tone_mapping, 0,
OPT_CHOICE_OR_INT("target-peak", target_peak, 0, 10, 10000,
({"auto", 0})),
OPT_CHOICE("tone-mapping", tone_map.curve, 0,
({"clip", TONE_MAPPING_CLIP},
{"mobius", TONE_MAPPING_MOBIUS},
{"reinhard", TONE_MAPPING_REINHARD},
{"hable", TONE_MAPPING_HABLE},
{"gamma", TONE_MAPPING_GAMMA},
{"linear", TONE_MAPPING_LINEAR})),
OPT_CHOICE("hdr-compute-peak", compute_hdr_peak, 0,
OPT_CHOICE("hdr-compute-peak", tone_map.compute_peak, 0,
({"auto", 0},
{"yes", 1},
{"no", -1})),
OPT_FLOAT("tone-mapping-param", tone_mapping_param, 0),
OPT_FLOAT("tone-mapping-desaturate", tone_mapping_desat, 0),
OPT_FLAG("gamut-warning", gamut_warning, 0),
OPT_FLOATRANGE("hdr-peak-decay-rate", tone_map.decay_rate, 0, 1.0, 1000.0),
OPT_FLOATRANGE("hdr-scene-threshold-low",
tone_map.scene_threshold_low, 0, 0, 20.0),
OPT_FLOATRANGE("hdr-scene-threshold-high",
tone_map.scene_threshold_high, 0, 0, 20.0),
OPT_FLOAT("tone-mapping-param", tone_map.curve_param, 0),
OPT_FLOATRANGE("tone-mapping-max-boost", tone_map.max_boost, 0, 1.0, 10.0),
OPT_FLOAT("tone-mapping-desaturate", tone_map.desat, 0),
OPT_FLOATRANGE("tone-mapping-desaturate-exponent",
tone_map.desat_exp, 0, 0.0, 20.0),
OPT_FLAG("gamut-warning", tone_map.gamut_warning, 0),
OPT_FLAG("opengl-pbo", pbo, 0),
SCALER_OPTS("scale", SCALER_SCALE),
SCALER_OPTS("dscale", SCALER_DSCALE),
@ -2056,6 +2072,23 @@ static void pass_read_video(struct gl_video *p)
}
}
// The basic idea is we assume the rgb/luma texture is the "reference" and
// scale everything else to match, after all planes are finalized.
// We find the reference texture first, in order to maintain texture offset
// between hooks on different type of planes.
int reference_tex_num = 0;
for (int n = 0; n < 4; n++) {
switch (img[n].type) {
case PLANE_RGB:
case PLANE_XYZ:
case PLANE_LUMA: break;
default: continue;
}
reference_tex_num = n;
break;
}
// Dispatch the hooks for all of these textures, saving and perhaps
// modifying them in the process
for (int n = 0; n < 4; n++) {
@ -2070,26 +2103,18 @@ static void pass_read_video(struct gl_video *p)
}
img[n] = pass_hook(p, name, img[n], &offsets[n]);
if (reference_tex_num == n) {
// The reference texture is finalized now.
p->texture_w = img[n].w;
p->texture_h = img[n].h;
p->texture_offset = offsets[n];
}
}
// At this point all planes are finalized but they may not be at the
// required size yet. Furthermore, they may have texture offsets that
// require realignment. For lack of something better to do, we assume
// the rgb/luma texture is the "reference" and scale everything else
// to match.
for (int n = 0; n < 4; n++) {
switch (img[n].type) {
case PLANE_RGB:
case PLANE_XYZ:
case PLANE_LUMA: break;
default: continue;
}
p->texture_w = img[n].w;
p->texture_h = img[n].h;
p->texture_offset = offsets[n];
break;
}
// require realignment.
// Compute the reference rect
struct mp_rect_f src = {0.0, 0.0, p->image_params.w, p->image_params.h};
@ -2365,6 +2390,7 @@ static void pass_scale_main(struct gl_video *p)
// values at 1 and 0, and then scale/shift them, respectively.
sig_offset = 1.0/(1+expf(sig_slope * sig_center));
sig_scale = 1.0/(1+expf(sig_slope * (sig_center-1))) - sig_offset;
GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);)
GLSLF("color.rgb = %f - log(1.0/(color.rgb * %f + %f) - 1.0) * 1.0/%f;\n",
sig_center, sig_scale, sig_offset, sig_slope);
pass_opt_hook_point(p, "SIGMOID", NULL);
@ -2392,6 +2418,7 @@ static void pass_scale_main(struct gl_video *p)
GLSLF("// scaler post-conversion\n");
if (use_sigmoid) {
// Inverse of the transformation above
GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);)
GLSLF("color.rgb = (1.0/(1.0 + exp(%f * (%f - color.rgb))) - %f) * 1.0/%f;\n",
sig_slope, sig_center, sig_offset, sig_scale);
}
@ -2471,16 +2498,16 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool
if (!dst.sig_peak)
dst.sig_peak = mp_trc_nom_peak(dst.gamma);
bool detect_peak = p->opts.compute_hdr_peak >= 0 && mp_trc_is_hdr(src.gamma);
struct gl_tone_map_opts tone_map = p->opts.tone_map;
bool detect_peak = tone_map.compute_peak >= 0 && mp_trc_is_hdr(src.gamma)
&& src.sig_peak > dst.sig_peak;
if (detect_peak && !p->hdr_peak_ssbo) {
struct {
float average[2];
int32_t frame_sum;
uint32_t frame_max;
uint32_t counter;
uint32_t frame_idx;
uint32_t frame_num;
uint32_t frame_max[PEAK_DETECT_FRAMES+1];
uint32_t frame_sum[PEAK_DETECT_FRAMES+1];
uint32_t total_max;
uint32_t total_sum;
} peak_ssbo = {0};
struct ra_buf_params params = {
@ -2492,8 +2519,8 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool
p->hdr_peak_ssbo = ra_buf_create(ra, &params);
if (!p->hdr_peak_ssbo) {
MP_WARN(p, "Failed to create HDR peak detection SSBO, disabling.\n");
tone_map.compute_peak = p->opts.tone_map.compute_peak = -1;
detect_peak = false;
p->opts.compute_hdr_peak = -1;
}
}
@ -2501,22 +2528,15 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool
pass_describe(p, "detect HDR peak");
pass_is_compute(p, 8, 8, true); // 8x8 is good for performance
gl_sc_ssbo(p->sc, "PeakDetect", p->hdr_peak_ssbo,
"vec2 average;"
"int frame_sum;"
"uint frame_max;"
"uint counter;"
"uint frame_idx;"
"uint frame_num;"
"uint frame_max[%d];"
"uint frame_avg[%d];"
"uint total_max;"
"uint total_avg;",
PEAK_DETECT_FRAMES + 1,
PEAK_DETECT_FRAMES + 1
);
}
// Adapt from src to dst as necessary
pass_color_map(p->sc, src, dst, p->opts.tone_mapping,
p->opts.tone_mapping_param, p->opts.tone_mapping_desat,
detect_peak, p->opts.gamut_warning, p->use_linear && !osd);
pass_color_map(p->sc, p->use_linear && !osd, src, dst, &tone_map);
if (p->use_lut_3d) {
gl_sc_uniform_texture(p->sc, "lut_3d", p->lut_3d_texture);
@ -3502,9 +3522,9 @@ static bool check_dumb_mode(struct gl_video *p)
return false;
// otherwise, use auto-detection
if (o->target_prim || o->target_trc || o->correct_downscaling ||
o->linear_downscaling || o->linear_upscaling || o->sigmoid_upscaling ||
o->interpolation || o->blend_subs || o->deband || o->unsharp)
if (o->correct_downscaling || o->linear_downscaling ||
o->linear_upscaling || o->sigmoid_upscaling || o->interpolation ||
o->blend_subs || o->deband || o->unsharp)
return false;
// check remaining scalers (tscale is already implicitly excluded above)
for (int i = 0; i < SCALER_COUNT; i++) {
@ -3516,8 +3536,6 @@ static bool check_dumb_mode(struct gl_video *p)
}
if (o->user_shaders && o->user_shaders[0])
return false;
if (p->use_lut_3d)
return false;
return true;
}
@ -3582,12 +3600,12 @@ static void check_gl_features(struct gl_video *p)
}
bool have_compute_peak = have_compute && have_ssbo;
if (!have_compute_peak && p->opts.compute_hdr_peak >= 0) {
int msgl = p->opts.compute_hdr_peak == 1 ? MSGL_WARN : MSGL_V;
if (!have_compute_peak && p->opts.tone_map.compute_peak >= 0) {
int msgl = p->opts.tone_map.compute_peak == 1 ? MSGL_WARN : MSGL_V;
MP_MSG(p, msgl, "Disabling HDR peak computation (one or more of the "
"following is not supported: compute shaders=%d, "
"SSBO=%d).\n", have_compute, have_ssbo);
p->opts.compute_hdr_peak = -1;
p->opts.tone_map.compute_peak = -1;
}
p->forced_dumb_mode = p->opts.dumb_mode > 0 || !have_fbo || !have_texrg;
@ -3609,7 +3627,6 @@ static void check_gl_features(struct gl_video *p)
.alpha_mode = p->opts.alpha_mode,
.use_rectangle = p->opts.use_rectangle,
.background = p->opts.background,
.compute_hdr_peak = p->opts.compute_hdr_peak,
.dither_algo = p->opts.dither_algo,
.dither_depth = p->opts.dither_depth,
.dither_size = p->opts.dither_size,
@ -3617,12 +3634,13 @@ static void check_gl_features(struct gl_video *p)
.temporal_dither_period = p->opts.temporal_dither_period,
.tex_pad_x = p->opts.tex_pad_x,
.tex_pad_y = p->opts.tex_pad_y,
.tone_mapping = p->opts.tone_mapping,
.tone_mapping_param = p->opts.tone_mapping_param,
.tone_mapping_desat = p->opts.tone_mapping_desat,
.tone_map = p->opts.tone_map,
.early_flush = p->opts.early_flush,
.icc_opts = p->opts.icc_opts,
.hwdec_interop = p->opts.hwdec_interop,
.target_trc = p->opts.target_trc,
.target_prim = p->opts.target_prim,
.target_peak = p->opts.target_peak,
};
for (int n = 0; n < SCALER_COUNT; n++)
p->opts.scaler[n] = gl_video_opts_def.scaler[n];

View File

@ -95,8 +95,18 @@ enum tone_mapping {
TONE_MAPPING_LINEAR,
};
// How many frames to average over for HDR peak detection
#define PEAK_DETECT_FRAMES 63
struct gl_tone_map_opts {
int curve;
float curve_param;
float max_boost;
int compute_peak;
float decay_rate;
float scene_threshold_low;
float scene_threshold_high;
float desat;
float desat_exp;
int gamut_warning; // bool
};
struct gl_video_opts {
int dumb_mode;
@ -107,11 +117,7 @@ struct gl_video_opts {
int target_prim;
int target_trc;
int target_peak;
int tone_mapping;
int compute_hdr_peak;
float tone_mapping_param;
float tone_mapping_desat;
int gamut_warning;
struct gl_tone_map_opts tone_map;
int correct_downscaling;
int linear_downscaling;
int linear_upscaling;

View File

@ -380,7 +380,7 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
GLSLF("color.rgb = max(color.rgb - vec3(%f), vec3(0.0)) \n"
" / (vec3(%f) - vec3(%f) * color.rgb);\n",
PQ_C1, PQ_C2, PQ_C3);
GLSLF("color.rgb = pow(color.rgb, vec3(1.0/%f));\n", PQ_M1);
GLSLF("color.rgb = pow(color.rgb, vec3(%f));\n", 1.0 / PQ_M1);
// PQ's output range is 0-10000, but we need it to be relative to to
// MP_REF_WHITE instead, so rescale
GLSLF("color.rgb *= vec3(%f);\n", 10000 / MP_REF_WHITE);
@ -567,123 +567,106 @@ static void pass_inverse_ootf(struct gl_shader_cache *sc, enum mp_csp_light ligh
// under a typical presentation gamma of about 2.0.
static const float sdr_avg = 0.25;
// The threshold for which to consider an average luminance difference to be
// a sign of a scene change.
static const int scene_threshold = 0.2 * MP_REF_WHITE;
static void hdr_update_peak(struct gl_shader_cache *sc)
static void hdr_update_peak(struct gl_shader_cache *sc,
const struct gl_tone_map_opts *opts)
{
// For performance, we want to do as few atomic operations on global
// memory as possible, so use an atomic in shmem for the work group.
GLSLH(shared uint wg_sum;);
GLSL(wg_sum = 0;)
// Update the sig_peak/sig_avg from the old SSBO state
GLSL(if (average.y > 0.0) {)
GLSL( sig_avg = max(1e-3, average.x);)
GLSL( sig_peak = max(1.00, average.y);)
GLSL(})
// Have each thread update the work group sum with the local value
// Chosen to avoid overflowing on an 8K buffer
const float log_min = 1e-3, log_scale = 400.0, sig_scale = 10000.0;
// For performance, and to avoid overflows, we tally up the sub-results per
// pixel using shared memory first
GLSLH(shared int wg_sum;)
GLSLH(shared uint wg_max;)
GLSL(wg_sum = 0; wg_max = 0;)
GLSL(barrier();)
GLSLF("atomicAdd(wg_sum, uint(sig * %f));\n", MP_REF_WHITE);
GLSLF("float sig_log = log(max(sig_max, %f));\n", log_min);
GLSLF("atomicAdd(wg_sum, int(sig_log * %f));\n", log_scale);
GLSLF("atomicMax(wg_max, uint(sig_max * %f));\n", sig_scale);
// Have one thread per work group update the global atomics. We use the
// work group average even for the global sum, to make the values slightly
// more stable and smooth out tiny super-highlights.
// Have one thread per work group update the global atomics
GLSL(memoryBarrierShared();)
GLSL(barrier();)
GLSL(if (gl_LocalInvocationIndex == 0) {)
GLSL( uint wg_avg = wg_sum / (gl_WorkGroupSize.x * gl_WorkGroupSize.y);)
GLSL( atomicMax(frame_max[frame_idx], wg_avg);)
GLSL( atomicAdd(frame_avg[frame_idx], wg_avg);)
GLSL( int wg_avg = wg_sum / int(gl_WorkGroupSize.x * gl_WorkGroupSize.y);)
GLSL( atomicAdd(frame_sum, wg_avg);)
GLSL( atomicMax(frame_max, wg_max);)
GLSL( memoryBarrierBuffer();)
GLSL(})
const float refi = 1.0 / MP_REF_WHITE;
// Update the sig_peak/sig_avg from the old SSBO state
GLSL(uint num_wg = gl_NumWorkGroups.x * gl_NumWorkGroups.y;)
GLSL(if (frame_num > 0) {)
GLSLF(" float peak = %f * float(total_max) / float(frame_num);\n", refi);
GLSLF(" float avg = %f * float(total_avg) / float(frame_num);\n", refi);
GLSLF(" sig_peak = max(1.0, peak);\n");
GLSLF(" sig_avg = max(%f, avg);\n", sdr_avg);
GLSL(});
GLSL(barrier();)
// Finally, to update the global state, we increment a counter per dispatch
GLSL(memoryBarrierBuffer();)
GLSL(barrier();)
GLSL(uint num_wg = gl_NumWorkGroups.x * gl_NumWorkGroups.y;)
GLSL(if (gl_LocalInvocationIndex == 0 && atomicAdd(counter, 1) == num_wg - 1) {)
// Since we sum up all the workgroups, we also still need to divide the
// average by the number of work groups
GLSL( counter = 0;)
GLSL( frame_avg[frame_idx] /= num_wg;)
GLSL( uint cur_max = frame_max[frame_idx];)
GLSL( uint cur_avg = frame_avg[frame_idx];)
GLSL( vec2 cur = vec2(float(frame_sum) / float(num_wg), frame_max);)
GLSLF(" cur *= vec2(1.0/%f, 1.0/%f);\n", log_scale, sig_scale);
GLSL( cur.x = exp(cur.x);)
GLSL( if (average.y == 0.0))
GLSL( average = cur;)
// Scene change detection
GLSL( int diff = int(frame_num * cur_avg) - int(total_avg);)
GLSLF(" if (abs(diff) > frame_num * %d) {\n", scene_threshold);
GLSL( frame_num = 0;)
GLSL( total_max = total_avg = 0;)
GLSLF(" for (uint i = 0; i < %d; i++)\n", PEAK_DETECT_FRAMES+1);
GLSL( frame_max[i] = frame_avg[i] = 0;)
GLSL( frame_max[frame_idx] = cur_max;)
GLSL( frame_avg[frame_idx] = cur_avg;)
GLSL( })
// Use an IIR low-pass filter to smooth out the detected values, with a
// configurable decay rate based on the desired time constant (tau)
float a = 1.0 - cos(1.0 / opts->decay_rate);
float decay = sqrt(a*a + 2*a) - a;
GLSLF(" average += %f * (cur - average);\n", decay);
// Add the current frame, then subtract and reset the next frame
GLSLF(" uint next = (frame_idx + 1) %% %d;\n", PEAK_DETECT_FRAMES+1);
GLSL( total_max += cur_max - frame_max[next];)
GLSL( total_avg += cur_avg - frame_avg[next];)
GLSL( frame_max[next] = frame_avg[next] = 0;)
// Scene change hysteresis
float log_db = 10.0 / log(10.0);
GLSLF(" float weight = smoothstep(%f, %f, abs(log(cur.x / average.x)));\n",
opts->scene_threshold_low / log_db,
opts->scene_threshold_high / log_db);
GLSL( average = mix(average, cur, weight);)
// Update the index and count
GLSL( frame_idx = next;)
GLSLF(" frame_num = min(frame_num + 1, %d);\n", PEAK_DETECT_FRAMES);
// Reset SSBO state for the next frame
GLSL( frame_sum = 0; frame_max = 0;)
GLSL( memoryBarrierBuffer();)
GLSL(})
}
// Tone map from a known peak brightness to the range [0,1]. If ref_peak
// is 0, we will use peak detection instead
static void pass_tone_map(struct gl_shader_cache *sc, bool detect_peak,
static void pass_tone_map(struct gl_shader_cache *sc,
float src_peak, float dst_peak,
enum tone_mapping algo, float param, float desat)
const struct gl_tone_map_opts *opts)
{
GLSLF("// HDR tone mapping\n");
// To prevent discoloration due to out-of-bounds clipping, we need to make
// sure to reduce the value range as far as necessary to keep the entire
// signal in range, so tone map based on the brightest component.
GLSL(float sig = max(max(color.r, color.g), color.b);)
GLSL(int sig_idx = 0;)
GLSL(if (color[1] > color[sig_idx]) sig_idx = 1;)
GLSL(if (color[2] > color[sig_idx]) sig_idx = 2;)
GLSL(float sig_max = color[sig_idx];)
GLSLF("float sig_peak = %f;\n", src_peak);
GLSLF("float sig_avg = %f;\n", sdr_avg);
if (detect_peak)
hdr_update_peak(sc);
if (opts->compute_peak >= 0)
hdr_update_peak(sc, opts);
GLSLF("vec3 sig = color.rgb;\n");
// Rescale the variables in order to bring it into a representation where
// 1.0 represents the dst_peak. This is because all of the tone mapping
// algorithms are defined in such a way that they map to the range [0.0, 1.0].
if (dst_peak > 1.0) {
GLSLF("sig *= %f;\n", 1.0 / dst_peak);
GLSLF("sig_peak *= %f;\n", 1.0 / dst_peak);
GLSLF("sig *= 1.0/%f;\n", dst_peak);
GLSLF("sig_peak *= 1.0/%f;\n", dst_peak);
}
GLSL(float sig_orig = sig;)
GLSLF("float slope = min(1.0, %f / sig_avg);\n", sdr_avg);
GLSL(float sig_orig = sig[sig_idx];)
GLSLF("float slope = min(%f, %f / sig_avg);\n", opts->max_boost, sdr_avg);
GLSL(sig *= slope;)
GLSL(sig_peak *= slope;)
// Desaturate the color using a coefficient dependent on the signal.
// Do this after peak detection in order to prevent over-desaturating
// overly bright souces
if (desat > 0) {
float base = 0.18 * dst_peak;
GLSL(float luma = dot(dst_luma, color.rgb);)
GLSLF("float coeff = max(sig - %f, 1e-6) / max(sig, 1e-6);\n", base);
GLSLF("coeff = pow(coeff, %f);\n", 10.0 / desat);
GLSL(color.rgb = mix(color.rgb, vec3(luma), coeff);)
GLSL(sig = mix(sig, luma * slope, coeff);) // also make sure to update `sig`
}
switch (algo) {
float param = opts->curve_param;
switch (opts->curve) {
case TONE_MAPPING_CLIP:
GLSLF("sig = %f * sig;\n", isnan(param) ? 1.0 : param);
break;
@ -697,14 +680,15 @@ static void pass_tone_map(struct gl_shader_cache *sc, bool detect_peak,
GLSLF("float b = (j*j - 2.0*j*sig_peak + sig_peak) / "
"max(1e-6, sig_peak - 1.0);\n");
GLSLF("float scale = (b*b + 2.0*b*j + j*j) / (b-a);\n");
GLSL(sig = sig > j ? scale * (sig + a) / (sig + b) : sig;)
GLSLF("sig = mix(sig, scale * (sig + vec3(a)) / (sig + vec3(b)),"
" greaterThan(sig, vec3(j)));\n");
GLSLF("}\n");
break;
case TONE_MAPPING_REINHARD: {
float contrast = isnan(param) ? 0.5 : param,
offset = (1.0 - contrast) / contrast;
GLSLF("sig = sig / (sig + %f);\n", offset);
GLSLF("sig = sig / (sig + vec3(%f));\n", offset);
GLSLF("float scale = (sig_peak + %f) / sig_peak;\n", offset);
GLSL(sig *= scale;)
break;
@ -712,19 +696,25 @@ static void pass_tone_map(struct gl_shader_cache *sc, bool detect_peak,
case TONE_MAPPING_HABLE: {
float A = 0.15, B = 0.50, C = 0.10, D = 0.20, E = 0.02, F = 0.30;
GLSLHF("float hable(float x) {\n");
GLSLHF("return ((x * (%f*x + %f)+%f)/(x * (%f*x + %f) + %f)) - %f;\n",
A, C*B, D*E, A, B, D*F, E/F);
GLSLHF("vec3 hable(vec3 x) {\n");
GLSLHF("return (x * (%f*x + vec3(%f)) + vec3(%f)) / "
" (x * (%f*x + vec3(%f)) + vec3(%f)) "
" - vec3(%f);\n",
A, C*B, D*E,
A, B, D*F,
E/F);
GLSLHF("}\n");
GLSL(sig = hable(sig) / hable(sig_peak);)
GLSLF("sig = hable(max(vec3(0.0), sig)) / hable(vec3(sig_peak)).x;\n");
break;
}
case TONE_MAPPING_GAMMA: {
float gamma = isnan(param) ? 1.8 : param;
GLSLF("const float cutoff = 0.05, gamma = %f;\n", 1.0/gamma);
GLSL(float scale = pow(cutoff / sig_peak, gamma) / cutoff;)
GLSL(sig = sig > cutoff ? pow(sig / sig_peak, gamma) : scale * sig;)
GLSLF("const float cutoff = 0.05, gamma = 1.0/%f;\n", gamma);
GLSL(float scale = pow(cutoff / sig_peak, gamma.x) / cutoff;)
GLSLF("sig = mix(scale * sig,"
" pow(sig / sig_peak, vec3(gamma)),"
" greaterThan(sig, vec3(cutoff)));\n");
break;
}
@ -738,24 +728,32 @@ static void pass_tone_map(struct gl_shader_cache *sc, bool detect_peak,
abort();
}
// Apply the computed scale factor to the color, linearly to prevent
// discoloration
GLSL(sig = min(sig, 1.0);)
GLSL(color.rgb *= vec3(sig / sig_orig);)
GLSL(sig = min(sig, vec3(1.0));)
GLSL(vec3 sig_lin = color.rgb * (sig[sig_idx] / sig_orig);)
// Mix between the per-channel tone mapped and the linear tone mapped
// signal based on the desaturation strength
if (opts->desat > 0) {
float base = 0.18 * dst_peak;
GLSLF("float coeff = max(sig[sig_idx] - %f, 1e-6) / "
" max(sig[sig_idx], 1.0);\n", base);
GLSLF("coeff = %f * pow(coeff, %f);\n", opts->desat, opts->desat_exp);
GLSLF("color.rgb = mix(sig_lin, %f * sig, coeff);\n", dst_peak);
} else {
GLSL(color.rgb = sig_lin;)
}
}
// Map colors from one source space to another. These source spaces must be
// known (i.e. not MP_CSP_*_AUTO), as this function won't perform any
// auto-guessing. If is_linear is true, we assume the input has already been
// linearized (e.g. for linear-scaling). If `detect_peak` is true, we will
// detect the peak instead of relying on metadata. Note that this requires
// the caller to have already bound the appropriate SSBO and set up the
// compute shader metadata
void pass_color_map(struct gl_shader_cache *sc,
// linearized (e.g. for linear-scaling). If `opts->compute_peak` is true, we
// will detect the peak instead of relying on metadata. Note that this requires
// the caller to have already bound the appropriate SSBO and set up the compute
// shader metadata
void pass_color_map(struct gl_shader_cache *sc, bool is_linear,
struct mp_colorspace src, struct mp_colorspace dst,
enum tone_mapping algo, float tone_mapping_param,
float tone_mapping_desat, bool detect_peak,
bool gamut_warning, bool is_linear)
const struct gl_tone_map_opts *opts)
{
GLSLF("// color mapping\n");
@ -791,6 +789,10 @@ void pass_color_map(struct gl_shader_cache *sc,
if (need_ootf)
pass_ootf(sc, src.light, src.sig_peak);
// Tone map to prevent clipping due to excessive brightness
if (src.sig_peak > dst.sig_peak)
pass_tone_map(sc, src.sig_peak, dst.sig_peak, opts);
// Adapt to the right colorspace if necessary
if (src.primaries != dst.primaries) {
struct mp_csp_primaries csp_src = mp_get_csp_primaries(src.primaries),
@ -801,13 +803,6 @@ void pass_color_map(struct gl_shader_cache *sc,
GLSL(color.rgb = cms_matrix * color.rgb;)
}
// Tone map to prevent clipping when the source signal peak exceeds the
// encodable range or we've reduced the gamut
if (src.sig_peak > dst.sig_peak) {
pass_tone_map(sc, detect_peak, src.sig_peak, dst.sig_peak, algo,
tone_mapping_param, tone_mapping_desat);
}
if (need_ootf)
pass_inverse_ootf(sc, dst.light, dst.sig_peak);
@ -821,8 +816,9 @@ void pass_color_map(struct gl_shader_cache *sc,
GLSLF("color.rgb *= vec3(%f);\n", 1.0 / dst_range);
// Warn for remaining out-of-gamut colors is enabled
if (gamut_warning) {
GLSL(if (any(greaterThan(color.rgb, vec3(1.01)))))
if (opts->gamut_warning) {
GLSL(if (any(greaterThan(color.rgb, vec3(1.01))) ||
any(lessThan(color.rgb, vec3(0.0)))))
GLSL(color.rgb = vec3(1.0) - color.rgb;) // invert
}

View File

@ -40,11 +40,9 @@ void pass_sample_oversample(struct gl_shader_cache *sc, struct scaler *scaler,
void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc);
void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc);
void pass_color_map(struct gl_shader_cache *sc,
void pass_color_map(struct gl_shader_cache *sc, bool is_linear,
struct mp_colorspace src, struct mp_colorspace dst,
enum tone_mapping algo, float tone_mapping_param,
float tone_mapping_desat, bool use_detected_peak,
bool gamut_warning, bool is_linear);
const struct gl_tone_map_opts *opts);
void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts,
AVLFG *lfg, enum mp_csp_trc trc);

View File

@ -262,6 +262,7 @@ static const struct gl_functions gl_functions[] = {
},
{
.ver_core = 320,
.ver_es_core = 300,
.extension = "GL_ARB_sync",
.functions = (const struct gl_function[]) {
DEF_FN(FenceSync),

View File

@ -50,8 +50,8 @@ struct gbm
{
struct gbm_surface *surface;
struct gbm_device *device;
struct gbm_bo *bo;
struct gbm_bo *next_bo;
struct gbm_bo **bo;
unsigned int num_bos;
};
struct egl
@ -72,6 +72,9 @@ struct priv {
struct gbm gbm;
struct framebuffer *fb;
GLsync *vsync_fences;
unsigned int num_vsync_fences;
uint32_t gbm_format;
bool active;
@ -80,6 +83,9 @@ struct priv {
bool vt_switcher_active;
struct vt_switcher vt_switcher;
bool still;
bool paused;
struct mpv_opengl_drm_params drm_params;
struct mpv_opengl_drm_draw_surface_size draw_surface_size;
};
@ -355,15 +361,6 @@ static void crtc_release(struct ra_ctx *ctx)
return;
p->active = false;
// wait for current page flip
while (p->waiting_for_flip) {
int ret = drmHandleEvent(p->kms->fd, &p->ev);
if (ret) {
MP_ERR(ctx->vo, "drmHandleEvent failed: %i\n", ret);
break;
}
}
if (p->kms->atomic_context) {
if (p->kms->atomic_context->old_state.saved) {
if (!crtc_release_atomic(ctx))
@ -414,44 +411,19 @@ static void acquire_vt(void *data)
crtc_setup(ctx);
}
static bool drm_atomic_egl_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo)
{
struct priv *p = sw->ctx->priv;
if (p->kms->atomic_context) {
if (!p->kms->atomic_context->request) {
p->kms->atomic_context->request = drmModeAtomicAlloc();
p->drm_params.atomic_request_ptr = &p->kms->atomic_context->request;
}
return ra_gl_ctx_start_frame(sw, out_fbo);
}
return false;
}
static const struct ra_swapchain_fns drm_atomic_swapchain = {
.start_frame = drm_atomic_egl_start_frame,
};
static void drm_egl_swap_buffers(struct ra_ctx *ctx)
static void queue_flip(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
struct drm_atomic_context *atomic_ctx = p->kms->atomic_context;
int ret;
if (!p->active)
return;
eglSwapBuffers(p->egl.display, p->egl.surface);
p->gbm.next_bo = gbm_surface_lock_front_buffer(p->gbm.surface);
p->waiting_for_flip = true;
update_framebuffer_from_bo(ctx, p->gbm.next_bo);
if (atomic_ctx) {
drm_object_set_property(atomic_ctx->request, atomic_ctx->draw_plane, "FB_ID", p->fb->id);
drm_object_set_property(atomic_ctx->request, atomic_ctx->draw_plane, "CRTC_ID", atomic_ctx->crtc->id);
drm_object_set_property(atomic_ctx->request, atomic_ctx->draw_plane, "ZPOS", 1);
ret = drmModeAtomicCommit(p->kms->fd, atomic_ctx->request,
DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, NULL);
DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, p);
if (ret)
MP_WARN(ctx->vo, "Failed to commit atomic request (%d)\n", ret);
} else {
@ -461,30 +433,129 @@ static void drm_egl_swap_buffers(struct ra_ctx *ctx)
MP_WARN(ctx->vo, "Failed to queue page flip: %s\n", mp_strerror(errno));
}
}
// poll page flip finish event
const int timeout_ms = 3000;
struct pollfd fds[1] = { { .events = POLLIN, .fd = p->kms->fd } };
poll(fds, 1, timeout_ms);
if (fds[0].revents & POLLIN) {
ret = drmHandleEvent(p->kms->fd, &p->ev);
if (ret != 0) {
MP_ERR(ctx->vo, "drmHandleEvent failed: %i\n", ret);
p->waiting_for_flip = false;
return;
}
}
p->waiting_for_flip = false;
p->waiting_for_flip = true;
if (atomic_ctx) {
drmModeAtomicFree(atomic_ctx->request);
atomic_ctx->request = drmModeAtomicAlloc();
}
gbm_surface_release_buffer(p->gbm.surface, p->gbm.bo);
p->gbm.bo = p->gbm.next_bo;
}
static void wait_on_flip(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
// poll page flip finish event
while (p->waiting_for_flip) {
const int timeout_ms = 3000;
struct pollfd fds[1] = { { .events = POLLIN, .fd = p->kms->fd } };
poll(fds, 1, timeout_ms);
if (fds[0].revents & POLLIN) {
const int ret = drmHandleEvent(p->kms->fd, &p->ev);
if (ret != 0)
MP_ERR(ctx->vo, "drmHandleEvent failed: %i\n", ret);
}
}
}
static void swapchain_step(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
if (p->gbm.num_bos > 0 && p->gbm.bo[0])
gbm_surface_release_buffer(p->gbm.surface, p->gbm.bo[0]);
MP_TARRAY_REMOVE_AT(p->gbm.bo, p->gbm.num_bos, 0);
}
static void new_fence(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
if (p->gl.FenceSync) {
GLsync fence = p->gl.FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
if (fence)
MP_TARRAY_APPEND(p, p->vsync_fences, p->num_vsync_fences, fence);
}
}
static void wait_fence(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
while (p->num_vsync_fences && (p->num_vsync_fences >= p->gbm.num_bos)) {
p->gl.ClientWaitSync(p->vsync_fences[0], GL_SYNC_FLUSH_COMMANDS_BIT, 1e9);
p->gl.DeleteSync(p->vsync_fences[0]);
MP_TARRAY_REMOVE_AT(p->vsync_fences, p->num_vsync_fences, 0);
}
}
static bool drm_egl_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo)
{
struct ra_ctx *ctx = sw->ctx;
struct priv *p = ctx->priv;
if (p->kms->atomic_context && !p->kms->atomic_context->request) {
p->kms->atomic_context->request = drmModeAtomicAlloc();
p->drm_params.atomic_request_ptr = &p->kms->atomic_context->request;
}
return ra_gl_ctx_start_frame(sw, out_fbo);
}
static bool drm_egl_submit_frame(struct ra_swapchain *sw, const struct vo_frame *frame)
{
struct ra_ctx *ctx = sw->ctx;
struct priv *p = ctx->priv;
p->still = frame->still;
return ra_gl_ctx_submit_frame(sw, frame);
}
static void drm_egl_swap_buffers(struct ra_swapchain *sw)
{
struct ra_ctx *ctx = sw->ctx;
struct priv *p = ctx->priv;
const bool drain = p->paused || p->still; // True when we need to drain the swapchain
if (!p->active)
return;
wait_fence(ctx);
eglSwapBuffers(p->egl.display, p->egl.surface);
struct gbm_bo *new_bo = gbm_surface_lock_front_buffer(p->gbm.surface);
if (!new_bo) {
MP_ERR(ctx->vo, "Couldn't lock front buffer\n");
return;
}
MP_TARRAY_APPEND(p, p->gbm.bo, p->gbm.num_bos, new_bo);
new_fence(ctx);
while (drain || p->gbm.num_bos > ctx->opts.swapchain_depth || !gbm_surface_has_free_buffers(p->gbm.surface)) {
if (p->waiting_for_flip) {
wait_on_flip(ctx);
swapchain_step(ctx);
}
if (p->gbm.num_bos <= 1)
break;
if (!p->gbm.bo[1]) {
MP_ERR(ctx->vo, "Hole in swapchain?\n");
swapchain_step(ctx);
continue;
}
update_framebuffer_from_bo(ctx, p->gbm.bo[1]);
queue_flip(ctx);
}
}
static const struct ra_swapchain_fns drm_egl_swapchain = {
.start_frame = drm_egl_start_frame,
.submit_frame = drm_egl_submit_frame,
.swap_buffers = drm_egl_swap_buffers,
};
static void drm_egl_uninit(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
@ -503,6 +574,12 @@ static void drm_egl_uninit(struct ra_ctx *ctx)
if (p->vt_switcher_active)
vt_switcher_destroy(&p->vt_switcher);
// According to GBM documentation all BO:s must be released before
// gbm_surface_destroy can be called on the surface.
while (p->gbm.num_bos) {
swapchain_step(ctx);
}
eglMakeCurrent(p->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
eglDestroyContext(p->egl.display, p->egl.context);
@ -565,6 +642,13 @@ static bool probe_gbm_format(struct ra_ctx *ctx, uint32_t argb_format, uint32_t
return result;
}
static void page_flipped(int fd, unsigned int frame, unsigned int sec,
unsigned int usec, void *data)
{
struct priv *p = data;
p->waiting_for_flip = false;
}
static bool drm_egl_init(struct ra_ctx *ctx)
{
if (ctx->opts.probing) {
@ -574,6 +658,7 @@ static bool drm_egl_init(struct ra_ctx *ctx)
struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
p->ev.version = DRM_EVENT_CONTEXT_VERSION;
p->ev.page_flip_handler = page_flipped;
p->vt_switcher_active = vt_switcher_init(&p->vt_switcher, ctx->vo->log);
if (p->vt_switcher_active) {
@ -644,12 +729,13 @@ static bool drm_egl_init(struct ra_ctx *ctx)
eglSwapBuffers(p->egl.display, p->egl.surface);
MP_VERBOSE(ctx, "Preparing framebuffer\n");
p->gbm.bo = gbm_surface_lock_front_buffer(p->gbm.surface);
if (!p->gbm.bo) {
struct gbm_bo *new_bo = gbm_surface_lock_front_buffer(p->gbm.surface);
if (!new_bo) {
MP_ERR(ctx, "Failed to lock GBM surface.\n");
return false;
}
update_framebuffer_from_bo(ctx, p->gbm.bo);
MP_TARRAY_APPEND(p, p->gbm.bo, p->gbm.num_bos, new_bo);
update_framebuffer_from_bo(ctx, new_bo);
if (!p->fb || !p->fb->id) {
MP_ERR(ctx, "Failed to create framebuffer.\n");
return false;
@ -681,9 +767,7 @@ static bool drm_egl_init(struct ra_ctx *ctx)
}
struct ra_gl_ctx_params params = {
.swap_buffers = drm_egl_swap_buffers,
.external_swapchain = p->kms->atomic_context ? &drm_atomic_swapchain :
NULL,
.external_swapchain = &drm_egl_swapchain,
};
if (!ra_gl_ctx_init(ctx, &p->gl, params))
return false;
@ -715,6 +799,13 @@ static int drm_egl_control(struct ra_ctx *ctx, int *events, int request,
*(double*)arg = fps;
return VO_TRUE;
}
case VOCTRL_PAUSE:
ctx->vo->want_redraw = true;
p->paused = true;
return VO_TRUE;
case VOCTRL_RESUME:
p->paused = false;
return VO_TRUE;
}
return VO_NOTIMPL;
}

View File

@ -35,13 +35,13 @@
// Generated from xdg-decoration-unstable-v1.xml
#include "video/out/wayland/xdg-decoration-v1.h"
static void xdg_shell_ping(void *data, struct xdg_wm_base *shell, uint32_t serial)
static void xdg_wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial)
{
xdg_wm_base_pong(shell, serial);
xdg_wm_base_pong(wm_base, serial);
}
static const struct xdg_wm_base_listener xdg_shell_listener = {
xdg_shell_ping,
static const struct xdg_wm_base_listener xdg_wm_base_listener = {
xdg_wm_base_ping,
};
static int spawn_cursor(struct vo_wayland_state *wl)
@ -806,8 +806,8 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id
if (!strcmp(interface, xdg_wm_base_interface.name) && found++) {
ver = MPMIN(ver, 2); /* We can use either 1 or 2 */
wl->shell = wl_registry_bind(reg, id, &xdg_wm_base_interface, ver);
xdg_wm_base_add_listener(wl->shell, &xdg_shell_listener, wl);
wl->wm_base = wl_registry_bind(reg, id, &xdg_wm_base_interface, ver);
xdg_wm_base_add_listener(wl->wm_base, &xdg_wm_base_listener, wl);
}
if (!strcmp(interface, wl_seat_interface.name) && found++) {
@ -956,7 +956,7 @@ static const struct xdg_toplevel_listener xdg_toplevel_listener = {
static int create_xdg_surface(struct vo_wayland_state *wl)
{
wl->xdg_surface = xdg_wm_base_get_xdg_surface(wl->shell, wl->surface);
wl->xdg_surface = xdg_wm_base_get_xdg_surface(wl->wm_base, wl->surface);
xdg_surface_add_listener(wl->xdg_surface, &xdg_surface_listener, wl);
wl->xdg_toplevel = xdg_surface_get_toplevel(wl->xdg_surface);
@ -1013,7 +1013,7 @@ int vo_wayland_init(struct vo *vo)
/* Do a roundtrip to run the registry */
wl_display_roundtrip(wl->display);
if (!wl->shell) {
if (!wl->wm_base) {
MP_FATAL(wl, "Compositor doesn't support the required %s protocol!\n",
xdg_wm_base_interface.name);
return false;
@ -1078,8 +1078,8 @@ void vo_wayland_uninit(struct vo *vo)
if (wl->idle_inhibit_manager)
zwp_idle_inhibit_manager_v1_destroy(wl->idle_inhibit_manager);
if (wl->shell)
xdg_wm_base_destroy(wl->shell);
if (wl->wm_base)
xdg_wm_base_destroy(wl->wm_base);
if (wl->shm)
wl_shm_destroy(wl->shm);

View File

@ -70,7 +70,7 @@ struct vo_wayland_state {
/* Shell */
struct wl_surface *surface;
struct xdg_wm_base *shell;
struct xdg_wm_base *wm_base;
struct xdg_toplevel *xdg_toplevel;
struct xdg_surface *xdg_surface;
struct zxdg_decoration_manager_v1 *xdg_decoration_manager;

View File

@ -164,6 +164,7 @@ def build(ctx):
if ctx.dependency_satisfied('macos-cocoa-cb'):
swift_source = [
( "osdep/macOS_mpv_helper.swift" ),
( "osdep/macOS_swift_extensions.swift" ),
( "video/out/cocoa-cb/events_view.swift" ),
( "video/out/cocoa-cb/video_layer.swift" ),
( "video/out/cocoa-cb/window.swift" ),
@ -234,12 +235,10 @@ def build(ctx):
( "audio/out/ao_alsa.c", "alsa" ),
( "audio/out/ao_audiounit.m", "audiounit" ),
( "audio/out/ao_coreaudio.c", "coreaudio" ),
( "audio/out/ao_coreaudio_chmap.c", "audiounit" ),
( "audio/out/ao_coreaudio_chmap.c", "coreaudio" ),
( "audio/out/ao_coreaudio_chmap.c", "coreaudio || audiounit" ),
( "audio/out/ao_coreaudio_exclusive.c", "coreaudio" ),
( "audio/out/ao_coreaudio_properties.c", "coreaudio" ),
( "audio/out/ao_coreaudio_utils.c", "audiounit" ),
( "audio/out/ao_coreaudio_utils.c", "coreaudio" ),
( "audio/out/ao_coreaudio_utils.c", "coreaudio || audiounit" ),
( "audio/out/ao_jack.c", "jack" ),
( "audio/out/ao_lavc.c" ),
( "audio/out/ao_null.c" ),