diff --git a/NEWS b/NEWS
index 5976541d8..ac9ca0788 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,8 @@ ver 0.21 (not yet released)
 ver 0.20.11 (not yet released)
 * storage
   - curl: support Content-Type application/xml
+* decoder
+  - ffmpeg: more reliable song duration
 
 ver 0.20.10 (2017/08/24)
 * decoder
diff --git a/doc/user.xml b/doc/user.xml
index c5c7d341b..e010d5e3e 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -2991,6 +2991,60 @@ run</programlisting>
         </informaltable>
       </section>
 
+      <section id="opus_encoder">
+        <title><varname>opus</varname></title>
+
+        <para>
+          Encodes into <ulink
+          url="http://www.opus-codec.org/">Ogg Opus</ulink>.
+        </para>
+
+        <informaltable>
+          <tgroup cols="2">
+            <thead>
+              <row>
+                <entry>Setting</entry>
+                <entry>Description</entry>
+              </row>
+            </thead>
+            <tbody>
+              <row>
+                <entry>
+                  <varname>bitrate</varname>
+                </entry>
+                <entry>
+                  Sets the data rate in bit per second.  The special
+                  value "auto" lets <application>libopus</application>
+                  choose a rate (which is the default), and "max" uses
+                  the maximum possible data rate.
+                </entry>
+              </row>
+
+              <row>
+                <entry>
+                  <varname>complexity</varname>
+                </entry>
+                <entry>
+                  Sets the <ulink
+                  url="https://wiki.xiph.org/OpusFAQ#What_is_the_complexity_of_Opus.3F">Opus
+                  complexity</ulink>.
+                </entry>
+              </row>
+
+              <row>
+                <entry>
+                  <varname>signal</varname>
+                </entry>
+                <entry>
+                  Sets the Opus signal type.  Valid values are "auto"
+                  (the default), "voice" and "music".
+                </entry>
+              </row>
+            </tbody>
+          </tgroup>
+        </informaltable>
+      </section>
+
       <section id="vorbis_encoder">
         <title><varname>vorbis</varname></title>
 
diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
index a035e058c..73d18e4ce 100644
--- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx
+++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
@@ -712,7 +712,9 @@ FfmpegDecode(DecoderClient &client, InputStream &input,
 #endif
 
 	const SignedSongTime total_time =
-		FromFfmpegTimeChecked(av_stream.duration, av_stream.time_base);
+		av_stream.duration != (int64_t)AV_NOPTS_VALUE
+		? FromFfmpegTimeChecked(av_stream.duration, av_stream.time_base)
+		: FromFfmpegTimeChecked(format_context.duration, AV_TIME_BASE_Q);
 
 	client.Ready(audio_format, input.IsSeekable(), total_time);
 
@@ -842,6 +844,10 @@ FfmpegScanStream(AVFormatContext &format_context,
 		tag_handler_invoke_duration(handler, handler_ctx,
 					    FromFfmpegTime(stream.duration,
 							   stream.time_base));
+	else if (format_context.duration != (int64_t)AV_NOPTS_VALUE)
+		tag_handler_invoke_duration(handler, handler_ctx,
+					    FromFfmpegTime(format_context.duration,
+							   AV_TIME_BASE_Q));
 
 	FfmpegScanMetadata(format_context, audio_stream, handler, handler_ctx);
 
diff --git a/src/output/plugins/SndioOutputPlugin.cxx b/src/output/plugins/SndioOutputPlugin.cxx
index 1ae92f1bf..285882665 100644
--- a/src/output/plugins/SndioOutputPlugin.cxx
+++ b/src/output/plugins/SndioOutputPlugin.cxx
@@ -66,16 +66,14 @@ SndioOutput::Create(EventLoop &, const ConfigBlock &block) {
 static bool
 sndio_test_default_device()
 {
-	struct sio_hdl *sio_hdl;
-
-	sio_hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0);
-	if (!sio_hdl) {
+	auto *hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0);
+	if (!hdl) {
 		FormatError(sndio_output_domain,
-		            "Error opening default sndio device");
+			    "Error opening default sndio device");
 		return false;
 	}
 
-	sio_close(sio_hdl);
+	sio_close(hdl);
 	return true;
 }
 
@@ -85,8 +83,8 @@ SndioOutput::Open(AudioFormat &audio_format)
 	struct sio_par par;
 	unsigned bits, rate, chans;
 
-	sio_hdl = sio_open(device, SIO_PLAY, 0);
-	if (!sio_hdl)
+	hdl = sio_open(device, SIO_PLAY, 0);
+	if (!hdl)
 		throw std::runtime_error("Failed to open default sndio device");
 
 	switch (audio_format.format) {
@@ -116,9 +114,9 @@ SndioOutput::Open(AudioFormat &audio_format)
 	par.le = SIO_LE_NATIVE;
 	par.appbufsz = rate * buffer_time / 1000;
 
-	if (!sio_setpar(sio_hdl, &par) ||
-	    !sio_getpar(sio_hdl, &par)) {
-		sio_close(sio_hdl);
+	if (!sio_setpar(hdl, &par) ||
+	    !sio_getpar(hdl, &par)) {
+		sio_close(hdl);
 		throw std::runtime_error("Failed to set/get audio params");
 	}
 
@@ -128,21 +126,21 @@ SndioOutput::Open(AudioFormat &audio_format)
 	    par.pchan != chans ||
 	    par.sig != 1 ||
 	    par.le != SIO_LE_NATIVE) {
-		sio_close(sio_hdl);
+		sio_close(hdl);
 		throw std::runtime_error("Requested audio params cannot be satisfied");
 	}
 
 	// Set volume after opening fresh audio stream which does
 	// know nothing about previous audio streams.
-	sio_setvol(sio_hdl, raw_volume);
+	sio_setvol(hdl, raw_volume);
 	// sio_onvol returns 0 if no volume knob is available.
 	// This is the case on raw audio devices rather than
 	// the sndiod audio server.
-	if (sio_onvol(sio_hdl, VolumeCallback, this) == 0)
+	if (sio_onvol(hdl, VolumeCallback, this) == 0)
 		raw_volume = -1;
 
-	if (!sio_start(sio_hdl)) {
-		sio_close(sio_hdl);
+	if (!sio_start(hdl)) {
+		sio_close(hdl);
 		throw std::runtime_error("Failed to start audio device");
 	}
 }
@@ -150,7 +148,7 @@ SndioOutput::Open(AudioFormat &audio_format)
 void
 SndioOutput::Close()  noexcept
 {
-	sio_close(sio_hdl);
+	sio_close(hdl);
 }
 
 size_t
@@ -158,15 +156,16 @@ SndioOutput::Play(const void *chunk, size_t size)
 {
 	size_t n;
 
-	n = sio_write(sio_hdl, chunk, size);
-	if (n == 0 && sio_eof(sio_hdl) != 0)
+	n = sio_write(hdl, chunk, size);
+	if (n == 0 && sio_eof(hdl) != 0)
 		throw std::runtime_error("sndio write failed");
 	return n;
 }
 
 void
-SndioOutput::SetVolume(unsigned int volume) {
-	sio_setvol(sio_hdl, volume * SIO_MAXVOL / 100);
+SndioOutput::SetVolume(unsigned int volume)
+{
+	sio_setvol(hdl, volume * SIO_MAXVOL / 100);
 }
 
 static inline unsigned int
diff --git a/src/output/plugins/SndioOutputPlugin.hxx b/src/output/plugins/SndioOutputPlugin.hxx
index f98c07489..848918898 100644
--- a/src/output/plugins/SndioOutputPlugin.hxx
+++ b/src/output/plugins/SndioOutputPlugin.hxx
@@ -30,7 +30,7 @@ class SndioOutput final : AudioOutput {
 	MixerListener *listener = nullptr;
 	const char *const device;
 	const unsigned buffer_time; /* in ms */
-	struct sio_hdl *sio_hdl;
+	struct sio_hdl *hdl;
 	int raw_volume;
 
 public: