diff --git a/NEWS b/NEWS
index e654211b3..dae8a13eb 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,6 @@
 ver 0.22.1 (not yet released)
+* decoder
+  - opus: apply the OpusHead output gain even if there is no EBU R128 tag
 * output
   - alsa: don't deadlock when the ALSA driver is buggy
   - jack, pulse: reduce the delay when stopping or pausing playback
diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx
index 4257b31e5..e3022d8fc 100644
--- a/src/decoder/plugins/OpusDecoderPlugin.cxx
+++ b/src/decoder/plugins/OpusDecoderPlugin.cxx
@@ -123,6 +123,14 @@ class MPDOpusDecoder final : public OggDecoder {
 	 */
 	ogg_int64_t granulepos;
 
+	/**
+	 * Was DecoderClient::SubmitReplayGain() called?  We need to
+	 * keep track of this, because it will usually be called by
+	 * HandleTags(), but if there is no OpusTags packet, we need
+	 * to submit our #output_gain value from the OpusHead.
+	 */
+	bool submitted_replay_gain = false;
+
 public:
 	explicit MPDOpusDecoder(DecoderReader &reader)
 		:OggDecoder(reader) {}
@@ -269,6 +277,7 @@ MPDOpusDecoder::HandleTags(const ogg_packet &packet)
 		return;
 
 	client.SubmitReplayGain(&rgi);
+	submitted_replay_gain = true;
 
 	if (!tag_builder.empty()) {
 		Tag tag = tag_builder.Commit();
@@ -283,6 +292,18 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
 {
 	assert(opus_decoder != nullptr);
 
+	if (!submitted_replay_gain) {
+		/* if we didn't see an OpusTags packet with EBU R128
+		   values, we still need to apply the output gain
+		   value from the OpusHead packet; submit it as "track
+		   gain" value */
+		ReplayGainInfo rgi;
+		rgi.Clear();
+		rgi.track.gain = EbuR128ToReplayGain(output_gain);
+		client.SubmitReplayGain(&rgi);
+		submitted_replay_gain = true;
+	}
+
 	int nframes = opus_decode(opus_decoder,
 				  (const unsigned char*)packet.packet,
 				  packet.bytes,