How do I tell what apps can change the media volume?
I have used aSpotCat in the past so I am recommending it for identifying apps by permission. Install and launch the app → tap on List apps by permission → tap on Misc. permissions → tap on three dots horizontal line icon → search "Change your audio settings".
This would list all the apps having permission to change the audio in the device globally.
Note: some users commented on the Play Store that the app shows too many ads, so I suggest using this app with internet connectivity disabled.
How do I tell what app is doing this?
This may be Android version dependent since I have tested this only on Android 8.1, but here's how you can find the troublesome app.
Setup adb in your PC and run this command on the PC as soon as you detect the spike in the audio output:
adb shell dumpsys audio
In the overwhelming output, search for the section beginning with the title Audio event log: volume changes. Observe the last written entries (by timestamp). In my test case I launched a video player app to play a video and changed the volume through it. Then I inserted my earphones which are automatically detected by an automation app MacroDroid and the latter automatically lowers the volume to protect my ears. Here's how all these volume changes were reflected in the log I got for my test case:
Audio event log: volume changes (logged when command received by AudioService)
07-22 05:03:57:687 adjustSuggestedStreamVolume(sugg:USE_DEFAULT_STREAM_TYPE dir:ADJUST_RAISE flags:0x1011) from android/MediaSessionService uid:1000
07-22 05:03:57:697 adjustSuggestedStreamVolume(sugg:USE_DEFAULT_STREAM_TYPE dir:ADJUST_SAME flags:0x1014) from android/MediaSessionService uid:1000
07-22 05:04:03:982 adjustSuggestedStreamVolume(sugg:USE_DEFAULT_STREAM_TYPE dir:ADJUST_LOWER flags:0x1011) from android/MediaSessionService uid:1000
07-22 05:04:03:999 adjustSuggestedStreamVolume(sugg:USE_DEFAULT_STREAM_TYPE dir:ADJUST_SAME flags:0x1014) from android/MediaSessionService uid:1000
07-22 05:04:04:792 adjustSuggestedStreamVolume(sugg:USE_DEFAULT_STREAM_TYPE dir:ADJUST_LOWER flags:0x1011) from android/MediaSessionService uid:1000
07-22 05:04:04:809 adjustSuggestedStreamVolume(sugg:USE_DEFAULT_STREAM_TYPE dir:ADJUST_SAME flags:0x1014) from android/MediaSessionService uid:1000
07-22 05:05:09:860 setStreamVolume(stream:STREAM_MUSIC index:12 flags:0x0) from video.player.videoplayer
07-22 05:05:09:871 setStreamVolume(stream:STREAM_MUSIC index:13 flags:0x0) from video.player.videoplayer
07-22 05:05:09:905 setStreamVolume(stream:STREAM_MUSIC index:14 flags:0x0) from video.player.videoplayer
07-22 05:05:09:988 setStreamVolume(stream:STREAM_MUSIC index:15 flags:0x0) from video.player.videoplayer
07-22 05:05:10:492 setStreamVolume(stream:STREAM_MUSIC index:16 flags:0x0) from video.player.videoplayer
07-22 05:05:10:542 setStreamVolume(stream:STREAM_MUSIC index:17 flags:0x0) from video.player.videoplayer
07-22 05:05:10:693 setStreamVolume(stream:STREAM_MUSIC index:16 flags:0x0) from video.player.videoplayer
07-22 05:05:10:845 setStreamVolume(stream:STREAM_MUSIC index:14 flags:0x0) from video.player.videoplayer
07-22 05:05:17:772 setStreamVolume(stream:STREAM_MUSIC index:15 flags:0x0) from video.player.videoplayer
07-22 05:05:17:789 setStreamVolume(stream:STREAM_MUSIC index:16 flags:0x0) from video.player.videoplayer
07-22 05:05:18:275 setStreamVolume(stream:STREAM_MUSIC index:15 flags:0x0) from video.player.videoplayer
07-22 05:05:18:325 setStreamVolume(stream:STREAM_MUSIC index:14 flags:0x0) from video.player.videoplayer
07-22 05:05:18:392 setStreamVolume(stream:STREAM_MUSIC index:13 flags:0x0) from video.player.videoplayer
07-22 05:05:21:665 setStreamVolume(stream:STREAM_MUSIC index:6 flags:0x0) from com.arlosoft.macrodroid
In the highlighted lines, the term that follows after the word from is the package name of the app which triggered the change in the volume level, which in my test case are video.player.videoplayer (for my Video Player app) and com.arlosoft.macrodroid (MacroDroid app).
Whatsoever package name appears in your output, you can find the corresponding app name by using this answer of mine.