text:
import React from 'react';
import PropTypes from 'prop-types';

const KEY_CODES = {
  ENTER: 13,
  BACKSPACE: 8
};

function DefaultTagComponent(props) {
  const classesStr = props.classes ? (` ${props.classes}`) : '';
  const className = `tag${classesStr}`;

  return (

{props.item}

        {props.removeTagLabel}

  );
}

export default class TaggedInput extends React.Component {

  static getDerivedStateFromProps (nextProps) {
    return { tags: (nextProps.tags || []).slice(0) };
  }

  constructor (props) {
    super(props);

    this.state = {
      tags: (this.props.tags || []).slice(0),
      currentInput: null
    };

    this.onInputRef = this.onInputRef.bind(this);
    this.onRemoveTag = this.onRemoveTag.bind(this);
    this.onEditTag = this.onEditTag.bind(this);
    this.onKeyUp = this.onKeyUp.bind(this);
    this.onKeyDown = this.onKeyDown.bind(this);
    this.onChange = this.onChange.bind(this);
    this.onBlur = this.onBlur.bind(this);
    this.onClickOnWrapper = this.onClickOnWrapper.bind(this);
    this._validateAndTag = this._validateAndTag.bind(this);
  }

  componentDidMount () {
    // What should this do?
    if (this.props.autofocus && this.input != null) {
      this.input.focus();
    }
  }

  onInputRef (ref) {
    this.input = ref;
  }

  onRemoveTag (index) {
    if (this.props.onBeforeRemoveTag(index)) {
      const removedItems = this.state.tags.splice(index, 1);

      if (this.state.duplicateIndex) {
        this.setState({duplicateIndex: null}, () => {
          this.props.onRemoveTag && this.props.onRemoveTag(removedItems[0], this.state.tags);
        });
      } else {
        this.props.onRemoveTag && this.props.onRemoveTag(removedItems[0], this.state.tags);
        this.forceUpdate();
      }
    }
  }

  onEditTag (index) {
    if (this.state.currentInput) {
      const trimmedInput = this.state.currentInput.trim();
      if (trimmedInput && (this.state.tags.indexOf(trimmedInput) < 0 || !this.props.unique)) {
        this._validateAndTag(this.state.currentInput);
      }
    }

    const removedItems = this.state.tags.splice(index, 1);
    if (this.state.duplicateIndex) {
      this.setState({duplicateIndex: null, currentInput: removedItems[0]}, () => {
        this.props.onRemoveTag && this.props.onRemoveTag(removedItems[0]);
      });
    } else {
      this.setState({currentInput: removedItems[0]}, () => {
        this.props.onRemoveTag && this.props.onRemoveTag(removedItems[0]);
      });
    }
  }

  onKeyUp (e) {
    //const enteredValue = e.target.value;

    if (e.keyCode === KEY_CODES.ENTER) {
      if (this.state.currentInput) {
        this._validateAndTag(this.state.currentInput, () => {
          if (this.props.onEnter) {
            this.props.onEnter(e, this.state.tags);
          }
        });
      }
    }
  }

  onKeyDown (e) {
    let poppedValue, newCurrentInput;

    if (e.keyCode === KEY_CODES.BACKSPACE) {
      if (!e.target.value || e.target.value.length < 0) {
        if (this.props.onBeforeRemoveTag(this.state.tags.length - 1)) {
          poppedValue = this.state.tags.pop();

          newCurrentInput = this.props.backspaceDeletesWord ? '' : poppedValue;

          this.setState({
            currentInput: newCurrentInput,
            duplicateIndex: null
          });
          if (this.props.onRemoveTag && poppedValue) {
            this.props.onRemoveTag(poppedValue);
          }
        }
      }
    }
  }

  onChange (e) {
    const value = e.target.value;
    const lastChar = value.charAt(value.length - 1);
    const tagText = value.substring(0, value.length - 1);

    if (this.props.delimiters.indexOf(lastChar) !== -1) {
      this._validateAndTag(tagText);
    } else {
      this.setState({
        currentInput: e.target.value
      });
    }
  }

  onBlur (e) {
    if (this.props.tagOnBlur) {
      const value = e.target.value;
      value && this._validateAndTag(value);
    }
  }

  onClickOnWrapper () {
    //this.refs.input;
  }

  getTags () {
    return this.state.tags;
  }

  getEnteredText () {
    return this.state.currentInput;
  }

  getAllValues () {
    if (this.state.currentInput && this.state.currentInput.length > 0) {
      return this.state.tags.concat(this.state.currentInput);
    } else {
      return this.state.tags;
    }
  }

  _validateAndTag (tagText, callback) {
    let duplicateIndex;
    let trimmedText;

    if (tagText && tagText.length > 0) {
      trimmedText = tagText.trim();
      if (this.props.unique) {

        // not a boolean, it's a function
        if (typeof this.props.unique === 'function') {
          duplicateIndex = this.props.unique(this.state.tags, trimmedText);
        } else {
          duplicateIndex = this.state.tags.indexOf(trimmedText);
        }

        if (duplicateIndex === -1) {
          if (!this.props.onBeforeAddTag(trimmedText)) {
            return;
          }

          this.state.tags.push(trimmedText);
          this.setState({
            currentInput: '',
            duplicateIndex: null
          }, () => {
            this.props.onAddTag && this.props.onAddTag(tagText, this.state.tags);
            callback && callback(true);
          });
        } else {
          this.setState({duplicateIndex: duplicateIndex}, () => {
            callback && callback(false);
          });
        }
      } else {
        if (!this.props.onBeforeAddTag(trimmedText)) {
          return;
        }

        this.state.tags.push(trimmedText);
        this.setState({currentInput: ''}, () => {
          this.props.onAddTag && this.props.onAddTag(tagText);
          callback && callback(true);
        });
      }
    }
  }

  render () {
    const tagComponents = [];
    let classes = 'tagged-input-wrapper';
    let placeholder;
    let i;

    if (this.props.classes) {
      classes += ` ${this.props.classes}`;
    }

    if (this.state.tags.length === 0) {
      placeholder = this.props.placeholder;
    }

    const TagComponent = DefaultTagComponent;

    for (i = 0; i < this.state.tags.length; i++) {
      tagComponents.push(
        <TagComponent
          key={`tag${i}`}
          item={this.state.tags[i]}
          itemIndex={i}
          onRemove={this.onRemoveTag.bind(this, i)}
          onEdit={this.props.clickTagToEdit ? this.onEditTag.bind(this, i) : null}
          classes={this.props.unique && (i === this.state.duplicateIndex) ? 'duplicate' : ''}
          removeTagLabel={this.props.removeTagLabel || '\u00D7'}
        />
      );
    }

    const input = (
      <input
        type="text"
        className={this.props.inputClassName}
        ref={this.onInputRef}
        onKeyUp={this.onKeyUp}
        onKeyDown={this.onKeyDown}
        onChange={this.onChange}
        onBlur={this.onBlur}
        value={this.state.currentInput || ''}
        placeholder={placeholder}
        tabIndex={this.props.tabIndex}>
      </input>
    );

    return (

        {tagComponents}
        {input}

    );
  }
}

TaggedInput.propTypes = {
  onBeforeAddTag: PropTypes.func,
  onAddTag: PropTypes.func,
  onBeforeRemoveTag: PropTypes.func,
  onRemoveTag: PropTypes.func,
  onEnter: PropTypes.func,
  unique: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
  autofocus: PropTypes.bool,
  backspaceDeletesWord: PropTypes.bool,
  placeholder: PropTypes.string,
  tags: PropTypes.arrayOf(PropTypes.any),
  removeTagLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
  delimiters: PropTypes.arrayOf((props, propName, componentName) => {
    if (typeof props[propName] !== 'string' || props[propName].length !== 1) {
      return new Error('TaggedInput prop delimiters must be an array of 1 character strings');
    }
  }),
  tagOnBlur: PropTypes.bool,
  tabIndex: PropTypes.number,
  clickTagToEdit: PropTypes.bool,
  inputClassName: PropTypes.string
};

TaggedInput.defaultProps = {
  delimiters: [' ', ','],
  unique: true,
  autofocus: false,
  backspaceDeletesWord: true,
  tagOnBlur: false,
  tabIndex: 0,
  tags: [],
  clickTagToEdit: false,
  onBeforeAddTag: () => {
    return true;
  },
  onAddTag: () => {

  },
  onBeforeRemoveTag: () => {
    return true;
  },
  placeholder: '',
  inputClassName: 'tagged-input'
};


text:
The **Platform** repository provides a toolkit to help orchestrate instances of the DigitalState **Application**. It uses a Docker container with **Ansible** pre-configured to help the developer manage local and remote deployments; from sandbox to production.

## Table of Contents

- [Architecture](#architecture)
- [Environments](#environments)
- [Playbooks](#playbooks)
- [Tutorials](#tutorials)
- [Build Status](#build-status)

## Architecture

- [General Architecture](architecture/general.md)
The Platform general architecture and core concepts.
- [Troubleshooting](architecture/troubleshooting.md)
Various strategies to help troubleshooting Ansible, Docker, Symfony, etc.

## Environments

- [Lab](environments/lab/index.md)
The lab environment is typically deployed to your local machine and is used to further develop the Platform.
- [CI](environments/ci/index.md)
The CI environment is typically deployed to a remote server and is used for testing continuous integration.
- [Development](environments/dev/index.md)
The development environment is typically deployed to your local machine and is used as sandbox.
- [Staging](environments/stag/index.md)
The staging environment is typically deployed to a remote server and is used as a pre-production mirror.
- [Production](environments/prod/index.md)
The production environment is typically deployed to a remote server and is used to serve the application.

## Playbooks

- [Structure](playbooks/structure.md)
The structure behind playbooks, tasks and roles.
- [List of Available Playbooks](playbooks/playbooks.md)
The list of available commands related to orchestration and development.

## Tutorials

- [Getting started with Postman](tutorials/postman.md/branches/develop.md)
This tutorial will help you get started with Postman and setup the collection and environment.

## Build Status

- [Develop Branch](status/branches/develop.md)
The build status of the latest development efforts.
- [Latest Release (0.13.0)](status/releases/0.13.0.md)
The build status of the latest release.
- [Previous Releases](status/releases/index.md)
The build statuses of previous releases.

redpajama_set_name: RedPajamaGithub


text:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using TailChaser.Entity;

namespace TailChaser.UI.UiHelpers
{
    public class ContextMenuHelper
    {
        public static ContextMenu GetContextMenu(object element, RoutedEventHandler clickHandler)
        {
            var contextMenu = new ContextMenu();
            var add = new MenuItem { Header = ContextMenuButtonType.Add.ToString(), CommandParameter = element };
            add.Click += clickHandler;
            var delete = new MenuItem { Header = ContextMenuButtonType.Delete.ToString(), CommandParameter = element };
            delete.Click += clickHandler;
            var settings = new MenuItem { Header = ContextMenuButtonType.Settings.ToString(), CommandParameter = element };
            settings.Click += clickHandler;

            if (element.GetType() == typeof(Machine) || element.GetType() == typeof(Group))
            {
                contextMenu.Items.Add(add);
                contextMenu.Items.Add(delete);
            }
            else if (element.GetType() == typeof(TailedFile))
            {
                contextMenu.Items.Add(settings);
                contextMenu.Items.Add(delete);
            }

            return contextMenu;
        }

    }
}


text:
package net.divlight.retainer.processor;

import java.lang.Override;
import net.divlight.retainer.Object;

public class Target$Inner1$Inner2_Retainer<T extends Target.Inner1.Inner2> implements Object<T> {
    private Object retainedObject2;

    @Override
    public void save(T target) {
        this.retainedObject2 = target.retainedObject2;
    }

    @Override
    public void restore(T target) {
        target.retainedObject2 = this.retainedObject2;
    }
}


text:
define(["knockout", "text!./TextFilterTemplate.html"], function (
  ko,
  componentTemplate
) {
  function TextFilterViewModel(params) {
    var self = this;
    self.Filter = params.Filter; // this will be a Text input type.

    self.operationOptions = [
      {
        id: "startsWith",
        name: ko.i18n('components.textFilter.startsWith', 'Starting With'),
      },
      {
        id: "contains",
        name: ko.i18n('components.textFilter.contains', 'Containing'),
      },
      {
        id: "endsWith",
        name: ko.i18n('components.textFilter.endsWith', 'Ending With'),
      },
      {
        id: "!startsWith",
        name: ko.i18n('components.textFilter.notStartsWith', 'Not Starting With'),
      },
      {
        id: "!contains",
        name: ko.i18n('components.textFilter.notContains', 'Not Containing'),
      },
      {
        id: "!endsWith",
        name: ko.i18n('components.textFilter.notEndsWith', 'Not Ending With'),
      },
    ];
  }

  // return compoonent definition
  return {
    viewModel: TextFilterViewModel,
    template: componentTemplate,
  };
});


text:
$packageName = '{{PackageName}}'
$fileType = 'exe'
$silentArgs = '/VERYSILENT'
$url = '{{DownloadUrl}}'
$url64bit = '{{DownloadUrlx64}}'

Install-ChocolateyPackage $packageName $fileType $silentArgs $url $url64bit


text:
namespace Bridge.Html5
{
    /// <summary>
    /// returns an unsigned short integer representing the type of the node.
    /// </summary>
    [External]
    [Enum(Emit.Value)]
    [Name("Number")]
    public enum NodeType
    {
        Element = 1,
        Attribute = 2,
        Text = 3,
        CDATA = 4,
        EntityReference = 5,
        Entity = 6,
        ProcessingInstruction = 7,
        Comment = 8,
        Document = 9,
        DocumentType = 10,
        DocumentFragment = 11,
        Notation = 12
    }
}


text:
namespace medialib
{

std::vector<float> synchronizeAudio(AudioBuffers &audioBuffers, float searchRange)
{
//	AudioBuffers audioBuffers;

//	for(size_t i=0; i<buffers.size(); ++i)
//		audioBuffers.push_back(AudioBuffer(buffers[i]));

	std::vector<float> timeDiff;

	if(audioBuffers.size()<2)
		return timeDiff;

	int maxSamples=std::ceil((float)audioBuffers[0].getSampleRate()*searchRange);
	//   size_t maxChannels=audioBuffers[0].getChannels();

	//get maximum segment size and maxium channels of all the audio buffers
	for(size_t i=1; i<audioBuffers.size(); ++i)
	{
		const AudioBuffer &audioBuffer=audioBuffers[i];

		//       int localSegementSize=std::ceil((float)audioBuffer.getSampleRate()*searchRange);

		maxSamples=std::max(maxSamples, (int)std::ceil((float)audioBuffer.getSampleRate()*searchRange));
		//       if(localSegementSize!=maxSegmentSize)
		//       {
		//           if(localSegementSize>maxSamples)
		//               maxSamples=localSegementSize;
		//       }

		//        if(audioBuffer.getChannels() > maxChannels)
		//            maxChannels=audioBuffer.getChannels();
	}

	size_t windowSize=1024;
	SpectrogramGenerator<float> spectrogramGenerator(windowSize, WindowFunctionType::Hamming, 0, true);
	size_t outputSize=spectrogramGenerator.calculateBufferSize(maxSamples);
	std::vector<float> spectrogram(outputSize);
	std::vector<float> maximum(outputSize);
	std::vector<uint8_t> binaryImage(outputSize);
	size_t width, height;

	for(size_t i=0; i<audioBuffers.size(); ++i)
	{
		const AudioBuffer &audioBuffer=audioBuffers[i];

		if(spectrogramGenerator.process(audioBuffer, 0, spectrogram.data(), outputSize, width, height))
//		if(spectrogramGenerator.process(audioBuffer.getBuffer(), audioBuffer.getSamples(), audioBuffer.getFormat(), spectrogram.data(), outputSize, width, height))
		{
			cvlib::SimpleImage imageSpectrogram(cvlib::ImageFormat::GreyScale32f, width, height, (uint8_t *)spectrogram.data(), outputSize*sizeof(float));

			std::string fileName="spectrogram"+std::to_string(i)+".ppm";
			cvlib::savePpm(imageSpectrogram, fileName);

//			cvlib::maximumSlidingWindow(spectrogram.data(), maximum.data(), width, height, 20);
//
//			cvlib::ImageStruct imageMaximum(width, height, cvlib::ImageFormat::GreyScale32f, (uint8_t *)maximum.data(), outputSize*sizeof(float));
//			cvlib::ImageStruct imageBinary(width, height, cvlib::ImageFormat::Binary, binaryImage.data(), outputSize*sizeof(uint8_t));
//
//			cvlib::match(imageSpectrogram, imageMaximum, imageBinary);
//
//			std::vector<std::pair<double, dlib::point>> maxPoints;
//			dlib::find_points_above_thresh(maxPoints, imageBinary, 0, std::numeric_limits<unsigned long>::max());
		}
	}

	return timeDiff;
}

//struct SampleMagnitude
//{
//	float magnitude;
//	size_t sampleIndex;
//};
//
//typedef std::unordered_map<size_t, SampleMagnitude> FrequencyBinMap;
//
//std::vector<float> synchronizeAudio(AudioBuffers audioBuffers, float searchRange, float segmentSeconds)
//{
//    std::vector<float> timeDiff;
//
//    if(audioBuffers.size()<2)
//        return timeDiff;
//
//    std::vector<int> segmentSizes;
//    typedef std::unordered_map<int, fftwf_plan> PlanMap;
//    PlanMap segmentPlans;
//    int maxSegmentSize=std::ceil((float)audioBuffers[0].sampleRate*segmentSeconds);
//	size_t maxChannels=audioBuffers[0].channels;
//
//	typedef std::unordered_map<int, > PlanMap;
//
//    segmentSizes.push_back(maxSegmentSize);
//
//    //get maximum segment size and maxium channels of all the audio buffers
//    for(size_t i=1; i<audioBuffers.size(); ++i)
//    {
//        AudioBuffer &audioBuffer=audioBuffers[i];
//
//        int localSegementSize=std::ceil((float)audioBuffer.sampleRate*segmentSeconds);
//
//        if(localSegementSize!=maxSegmentSize)
//        {
//            if(localSegementSize>maxSegmentSize)
//                maxSegmentSize=localSegementSize;
//
//            if(std::find(segmentSizes.begin(), segmentSizes.end(), localSegementSize) == segmentSizes.end())
//                segmentSizes.push_back(localSegementSize);
//        }
//
//		if(audioBuffer.channels > maxChannels)
//			maxChannels=audioBuffer.channels;
//    }
//
//	if(maxChannels <= 0)
//		return timeDiff;
//
//    int outSamples=(maxSegmentSize/2)+1;
//    size_t inBufferSize=sizeof(float) * maxSegmentSize;
//	std::vector<float *> inBuffers(maxChannels);
//
//	for(size_t i=0; i<maxChannels; ++i)
//		inBuffers[i]=(float *)fftwf_malloc(inBufferSize);
//	fftwf_complex *outBuffer=(fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * outSamples);
//
//    for(int &segmentSize:segmentSizes)
//    {
//        segmentPlans.insert(PlanMap::value_type(segmentSize, fftwf_plan_dft_r2c_1d(segmentSize, inBuffers[0], outBuffer, FFTW_ESTIMATE)));
//    }
//
//    for(size_t i=0; i<audioBuffers.size(); ++i)
//    {
//        AudioBuffer &audioBuffer=audioBuffers[i];
//
//        int localSegementSize=std::ceil((float)audioBuffer.sampleRate*segmentSeconds);
//        fftwf_plan &plan=segmentPlans[localSegementSize];
//        int segments=((float)audioBuffer.sampleRate*searchRange)/localSegementSize;
//
//		size_t offset=0;
//
//		for(size_t bufferIndex=0; bufferIndex<segments; ++bufferIndex)
//		{
//			if(audioBuffer.format == AudioFormat::Float) //in native float format no need to copy
//			{
//				assert(false); //need to implement this
//
////				fftwf_execute_dft_r2c(plan, in, out);
//			}
//			else
//			{
//				//move to in buffer for fft
//				convertToFloat(audioBuffer, offset, localSegementSize, inBuffers, inBufferSize);
//
//				for(size_t channel=0; channel<audioBuffer.channels; ++channel)
//				{
//					fftwf_execute_dft_r2c(plan, inBuffers[channel], outBuffer, localSegementSize);
//
//					pushFrequencies(frequencyMap, outBuffer);
//				}
//			}
//			offset+=localSegementSize;
//		}
//    }
//}
//
//void pushFrequencies(FrequencyBinMap &frequencyBinMap, fftwf_complex *buffer, size_t size)
//{
//	for(size_t i=0; i<size; ++i)
//	{
//		fftwf_complex &complex=buffer[i];
//		float sqrMagnitude=complex[0]*complex[0]+complex[1]*complex[1]; //using relative difference so sqrt not needed for actual magnitude
//
//
//		frequencyBinMap.insert(FrequencyBinMap)
//
//	}
//}

}//namespace medialib


text:
[![Build Status](https://travis-ci.org/fritzpay/paymentd.svg?branch=master)](https://travis-ci.org/fritzpay/paymentd)

paymentd
========

FritzPay paymentd is the payment server of the FritzPay stack.

# Install

Retrieve the sources for paymentd.

`$ go get -d github.com/fritzpay/paymentd`

You can use the `make.go` script.

```
$ cd $GOPATH/src/github.com/fritzpay/paymentd
$ go run make.go
```

## Manual Install:

If you don't have [Godep](https://github.com/tools/godep) installed.

If you have Godep installed, you can skip this step.

`$ go install github.com/tools/godep`

Restore the dependencies.

```
$ cd $GOPATH/src/github.com/fritzpay/paymentd
$ godep restore ./...
```

And build paymentd.

`$ go install github.com/fritzpay/paymentd/cmd/paymentd`


text:
.jp-FileEditor {
}

/*-----------------------------------------------------------------------------
| Presentation Mode (.jp-mod-presentationMode)
|----------------------------------------------------------------------------*/

.jp-mod-presentationMode .jp-FileEditor {
  --jp-code-font-size: var(--jp-code-presentation-font-size);
}


text:
Ohjelman komentorivikäyttöliittymä on toteutettu `src/main.c`-tiedostossa.
Ohjelma käsittelemät kuvat tallennetaan muistiin joko 24-bittisinä RGB kuvina
tai 24-bittisenä väripalettina ja sen indekseinä. Tähän liittyvä koodi on
`src/image`-hakemistossa. Käyttöliittymän kannalta tärkeää ovat `image/bmp.h`
tiedossa määritellyt funktiot, jotka mahdollistavat kuvien lukemisen levyltä ja
kirjoittamisen levylle BMP-tiedostomuodossa. Itse kvantisointialgoritmit ovat
toteutettu omissa tiedostoissaan `src/quantization`-hakemistossa.

## Aika ja tilavaativuudet

### Uniform

Uniform-algoritmi on yksinkertainen. Se käy kerran kaikki syötteen
pikselit läpi ja muuttaa ne indekseiksi ennalta määrätyssä väripaletissa. Se
siis toimii lineaarisessa ajassa. Algoritmin tilavaativuus on myös lineaarinen,
koska se luonnollisesti tarvitsee syötteen verran tilaa uudelle kuvalle ja
määrätyn kokoiselle paletille.

### Järjestys

Popularity ja median-cut algoritmeissa käytetään järjestystä. Tämä on
toteutettu Quicksort-algoritmilla. Tunnetusti sen aikavaativuus on O(n²), mutta
käytännössä se toimii usein paremmin ajassa O(n log n). Toteutuksen
tilavaativuus on vakio, koska taulukkoa muokatataan.

### Palette

Palette-algoritmia hyödynnetään popularity ja median-cut algorimeissa etsimään
jokaiselle kuvan pikselille lähin väri määrätystä paletista. Sen aikavaativuus
on O(n²), koska jos haluttua väriä ei suoraan löydy paletista, koko paletti
on käytävä läpi. Käytännössä paletin koko on pieni ja lähin väri pitäisi löytyä
melko nopeasti. Tilavaativuus on lineaarinen riippuen suoraan syötteen koosta.

### Popularity

Popularity-algoritmi käyttää suosituimpien värien etsimiseen järjestystä.
Ensiksi kuvan pikselit kopioidaan ja järjestetään. Tästä saadaan selville
värien määrä ja esiintyminen lineaarisessa ajassa. Tämän jälkeen värit
järjestetään esiintymisen mukaan, jolloin saadaan selville suosituimmat värit.
Tästä saadaan helposti luotua haluttu paletti ja voidaan ajaa
palette-algoritmi.

Paletin luominen onnistuu Quicksort-algoritmin mukaan ajassa O(n log n).
Palette-algoritmin aikavaativuus on kuitenkin O(n²).  Tilavaativuus on
lineaarinen, koska tilaa tarvitaan syötteen kopiolle/paletille,
esiintymistaulukolle ja syötettä vastaavalle tulosteelle.

Suorituskykyä voisi mahdollisesti parantaa käyttämällä hajautusta.

### Keko

Median-cut ja octree algoritmeissa käytetään kekoa. Se on toteutettu
binäärikekona. Tunnetusti insert ja delete operaatioiden aikavaativuus on O(log
n). Tietorakenteen binääripuu on toteutettu taulukkona, jonka tilavaativuus on
O(n).

### Median-cut

Median-cut-algoritmi jakaa väriavaruuden suurimman alueen kahtia sen suurimman
akselin mukaan. Koska yksi jakaa väriavaruuden yhteen uuteen osaan, voidaan tätä
n kertaa toistamalla saada n aluetta. Tämän jälkeen lasketaan alueiden värien
keskiarvot ja näin saadaan väripaletti. Lopuksi ajetaan palette-algoritmi.

Jaettavat alueet tallennetaan kekoon, josta saadaan nopeasti seuraavaksi
jaettava alue. Algoritmin aikavaativuus on siis O(n log n). Lopuksi ajetaan
palette-algoritmi, joten koko algoritmin aikavaativuus on O(n²). Tilavaativuus
on taas O(n), koska lopussa keossa on n aluetta.

Palette-algoritmin voisi korvata rakentamalla k-d tree -hakupuun. Tämä
kuitenkin vaatisi lisää tilaa, koska pelkkien lehtien rinnalla kokonainen puu
pitäisi tallentaa.

### Octree-tietorakenne

Octree-tietorakenne on 3-ulotteinen hakupuu, jossa jokaisella solmulla on 8
lasta. Insert-operaation aikavaativuus on vakio, koska alkio lisätään aina
samalle tasolle. Käytännössä operaation tulee mahdollisesti varata muistia.
Search-operaatio taas toimii ajassa O(log n). Mukana on myös reduce-operaatio,
joka poistaa lehden ja yhdistää sen vanhempaan. Tämä onnistuu vakioajassa.

### Octree-algoritmi

Octree-algoritmi lisää kaikki syötteen värit octree-tietorakenteeseen. Jos
värejä on tallennettu enemmän kuin haluttu määrä, on jokin väreistä poistettava
puusta.  Tähän käytetään reduce-operaatiota. Kun kaikki värit ovat lisätty,
voidaan lähin väri löytää search-operaatiolla.

Algoritmin aikavaativuus on O(n log n), koska tässä ajassa apuna käytettässä
keossa operaatiot tapahtuvat ja lähin väri voidaan löytää puusta jokaista
pikseliä kohden. Tilavaativuus on O(n), koska tilaa tarvitaan vain haluttujen
värien mukaan.


text:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using uWhiteLabel.Install;
using System.Xml;
using Umbraco.Core.IO;
using Umbraco.Core;

namespace Tests
{
    [TestClass]
    public class uWhiteLabelDashboardAction_Tests
    {
        [TestMethod]
        public void Execute_Undo_NoError()
        {
            var action = new uWhiteLabelDashboardAction();
            var result = action.Execute("uWhiteLabel", null);
            Assert.IsTrue(result);

            string dbConfig = SystemFiles.DashboardConfig;
            XmlDocument dashboardFile = XmlHelper.OpenAsXmlDocument(dbConfig);
            XmlNode section = dashboardFile.SelectSingleNode("//section[@alias='StartupDashboardSection']/tab[@caption='Welcome']");
            Assert.IsNotNull(section);

            result = action.Undo("uWhiteLabel", null);
            Assert.IsTrue(result);
        }
        [TestMethod]
        public void Undo_NoError()
        {
            var action = new uWhiteLabelDashboardAction();
            var result = action.Undo("uWhiteLabel", null);
            Assert.IsTrue(result);
        }
    }
}


text:
namespace content {

#define NOTIFY_ERROR(x)                                               \
  do {                                                                \
    SetDecoderState(kError);                                          \
    DLOG(ERROR) << "calling NotifyError(): " << x;                    \
    NotifyError(x);                                                   \
  } while (0)

#define IOCTL_OR_ERROR_RETURN(type, arg)                           \
  do {                                                             \
    if (HANDLE_EINTR(device_->Ioctl(type, arg) != 0)) {            \
      DPLOG(ERROR) << __func__ << "(): ioctl() failed: " << #type; \
      NOTIFY_ERROR(PLATFORM_FAILURE);                              \
      return;                                                      \
    }                                                              \
  } while (0)

#define IOCTL_OR_ERROR_RETURN_FALSE(type, arg)                     \
  do {                                                             \
    if (HANDLE_EINTR(device_->Ioctl(type, arg) != 0)) {            \
      DPLOG(ERROR) << __func__ << "(): ioctl() failed: " << #type; \
      NOTIFY_ERROR(PLATFORM_FAILURE);                              \
      return false;                                                \
    }                                                              \
  } while (0)

namespace {

// TODO(posciak): remove once we update linux-headers.
#ifndef V4L2_EVENT_RESOLUTION_CHANGE
#define V4L2_EVENT_RESOLUTION_CHANGE 5
#endif

}  // anonymous namespace

struct V4L2VideoDecodeAccelerator::BitstreamBufferRef {
  BitstreamBufferRef(
      base::WeakPtr<Client>& client,
      scoped_refptr<base::MessageLoopProxy>& client_message_loop_proxy,
      base::SharedMemory* shm,
      size_t size,
      int32 input_id);
  ~BitstreamBufferRef();
  const base::WeakPtr<Client> client;
  const scoped_refptr<base::MessageLoopProxy> client_message_loop_proxy;
  const scoped_ptr<base::SharedMemory> shm;
  const size_t size;
  off_t bytes_used;
  const int32 input_id;
};

struct V4L2VideoDecodeAccelerator::EGLSyncKHRRef {
  EGLSyncKHRRef(EGLDisplay egl_display, EGLSyncKHR egl_sync);
  ~EGLSyncKHRRef();
  EGLDisplay const egl_display;
  EGLSyncKHR egl_sync;
};

struct V4L2VideoDecodeAccelerator::PictureRecord {
  PictureRecord(bool cleared, const media::Picture& picture);
  ~PictureRecord();
  bool cleared;  // Whether the texture is cleared and safe to render from.
  media::Picture picture;  // The decoded picture.
};

V4L2VideoDecodeAccelerator::BitstreamBufferRef::BitstreamBufferRef(
    base::WeakPtr<Client>& client,
    scoped_refptr<base::MessageLoopProxy>& client_message_loop_proxy,
    base::SharedMemory* shm, size_t size, int32 input_id)
    : client(client),
      client_message_loop_proxy(client_message_loop_proxy),
      shm(shm),
      size(size),
      bytes_used(0),
      input_id(input_id) {
}

V4L2VideoDecodeAccelerator::BitstreamBufferRef::~BitstreamBufferRef() {
  if (input_id >= 0) {
    client_message_loop_proxy->PostTask(FROM_HERE, base::Bind(
        &Client::NotifyEndOfBitstreamBuffer, client, input_id));
  }
}

V4L2VideoDecodeAccelerator::EGLSyncKHRRef::EGLSyncKHRRef(
    EGLDisplay egl_display, EGLSyncKHR egl_sync)
    : egl_display(egl_display),
      egl_sync(egl_sync) {
}

V4L2VideoDecodeAccelerator::EGLSyncKHRRef::~EGLSyncKHRRef() {
  // We don't check for eglDestroySyncKHR failures, because if we get here
  // with a valid sync object, something went wrong and we are getting
  // destroyed anyway.
  if (egl_sync != EGL_NO_SYNC_KHR)
    eglDestroySyncKHR(egl_display, egl_sync);
}

V4L2VideoDecodeAccelerator::InputRecord::InputRecord()
    : at_device(false),
      address(NULL),
      length(0),
      bytes_used(0),
      input_id(-1) {
}

V4L2VideoDecodeAccelerator::InputRecord::~InputRecord() {
}

V4L2VideoDecodeAccelerator::OutputRecord::OutputRecord()
    : at_device(false),
      at_client(false),
      egl_image(EGL_NO_IMAGE_KHR),
      egl_sync(EGL_NO_SYNC_KHR),
      picture_id(-1),
      cleared(false) {
  for (size_t i = 0; i < arraysize(fds); ++i)
    fds[i] = -1;
}

V4L2VideoDecodeAccelerator::OutputRecord::~OutputRecord() {}

V4L2VideoDecodeAccelerator::PictureRecord::PictureRecord(
    bool cleared,
    const media::Picture& picture)
    : cleared(cleared), picture(picture) {}

V4L2VideoDecodeAccelerator::PictureRecord::~PictureRecord() {}

V4L2VideoDecodeAccelerator::V4L2VideoDecodeAccelerator(
    EGLDisplay egl_display,
    Client* client,
    const base::WeakPtr<Client>& io_client,
    const base::Callback<bool(void)>& make_context_current,
    scoped_ptr<V4L2Device> device,
    const scoped_refptr<base::MessageLoopProxy>& io_message_loop_proxy)
    : child_message_loop_proxy_(base::MessageLoopProxy::current()),
      io_message_loop_proxy_(io_message_loop_proxy),
      weak_this_(base::AsWeakPtr(this)),
      client_ptr_factory_(client),
      client_(client_ptr_factory_.GetWeakPtr()),
      io_client_(io_client),
      decoder_thread_("V4L2DecoderThread"),
      decoder_state_(kUninitialized),
      device_(device.Pass()),
      decoder_delay_bitstream_buffer_id_(-1),
      decoder_current_input_buffer_(-1),
      decoder_decode_buffer_tasks_scheduled_(0),
      decoder_frames_at_client_(0),
      decoder_flushing_(false),
      resolution_change_pending_(false),
      resolution_change_reset_pending_(false),
      decoder_partial_frame_pending_(false),
      input_streamon_(false),
      input_buffer_queued_count_(0),
      output_streamon_(false),
      output_buffer_queued_count_(0),
      output_buffer_pixelformat_(0),
      output_dpb_size_(0),
      picture_clearing_count_(0),
      pictures_assigned_(false, false),
      device_poll_thread_("V4L2DevicePollThread"),
      make_context_current_(make_context_current),
      egl_display_(egl_display),
      video_profile_(media::VIDEO_CODEC_PROFILE_UNKNOWN) {}

V4L2VideoDecodeAccelerator::~V4L2VideoDecodeAccelerator() {
  DCHECK(!decoder_thread_.IsRunning());
  DCHECK(!device_poll_thread_.IsRunning());

  DestroyInputBuffers();
  DestroyOutputBuffers();

  // These maps have members that should be manually destroyed, e.g. file
  // descriptors, mmap() segments, etc.
  DCHECK(input_buffer_map_.empty());
  DCHECK(output_buffer_map_.empty());
}

bool V4L2VideoDecodeAccelerator::Initialize(
    media::VideoCodecProfile profile) {
  DVLOG(3) << "Initialize()";
  DCHECK(child_message_loop_proxy_->BelongsToCurrentThread());
  DCHECK_EQ(decoder_state_, kUninitialized);

  switch (profile) {
    case media::H264PROFILE_BASELINE:
      DVLOG(2) << "Initialize(): profile H264PROFILE_BASELINE";
      break;
    case media::H264PROFILE_MAIN:
      DVLOG(2) << "Initialize(): profile H264PROFILE_MAIN";
      break;
    case media::H264PROFILE_HIGH:
      DVLOG(2) << "Initialize(): profile H264PROFILE_HIGH";
      break;
    case media::VP8PROFILE_MAIN:
      DVLOG(2) << "Initialize(): profile VP8PROFILE_MAIN";
      break;
    default:
      DLOG(ERROR) << "Initialize(): unsupported profile=" << profile;
      return false;
  };
  video_profile_ = profile;

  if (egl_display_ == EGL_NO_DISPLAY) {
    DLOG(ERROR) << "Initialize(): could not get EGLDisplay";
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return false;
  }

  // We need the context to be initialized to query extensions.
  if (!make_context_current_.Run()) {
    DLOG(ERROR) << "Initialize(): could not make context current";
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return false;
  }

  if (!gfx::g_driver_egl.ext.b_EGL_KHR_fence_sync) {
    DLOG(ERROR) << "Initialize(): context does not have EGL_KHR_fence_sync";
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return false;
  }

  // Capabilities check.
  struct v4l2_capability caps;
  const __u32 kCapsRequired =
      V4L2_CAP_VIDEO_CAPTURE_MPLANE |
      V4L2_CAP_VIDEO_OUTPUT_MPLANE |
      V4L2_CAP_STREAMING;
  IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QUERYCAP, &caps);
  if ((caps.capabilities & kCapsRequired) != kCapsRequired) {
    DLOG(ERROR) << "Initialize(): ioctl() failed: VIDIOC_QUERYCAP"
        ", caps check failed: 0x" << std::hex << caps.capabilities;
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return false;
  }

  if (!CreateInputBuffers())
    return false;

  // Output format has to be setup before streaming starts.
  struct v4l2_format format;
  memset(&format, 0, sizeof(format));
  format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
  format.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M;
  IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_FMT, &format);

  // Subscribe to the resolution change event.
  struct v4l2_event_subscription sub;
  memset(&sub, 0, sizeof(sub));
  sub.type = V4L2_EVENT_RESOLUTION_CHANGE;
  IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_SUBSCRIBE_EVENT, &sub);

  // Initialize format-specific bits.
  if (video_profile_ >= media::H264PROFILE_MIN &&
      video_profile_ <= media::H264PROFILE_MAX) {
    decoder_h264_parser_.reset(new media::H264Parser());
  }

  if (!decoder_thread_.Start()) {
    DLOG(ERROR) << "Initialize(): decoder thread failed to start";
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return false;
  }

  SetDecoderState(kInitialized);

  child_message_loop_proxy_->PostTask(FROM_HERE, base::Bind(
      &Client::NotifyInitializeDone, client_));
  return true;
}

void V4L2VideoDecodeAccelerator::Decode(
    const media::BitstreamBuffer& bitstream_buffer) {
  DVLOG(1) << "Decode(): input_id=" << bitstream_buffer.id()
           << ", size=" << bitstream_buffer.size();
  DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());

  // DecodeTask() will take care of running a DecodeBufferTask().
  decoder_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
      &V4L2VideoDecodeAccelerator::DecodeTask, base::Unretained(this),
      bitstream_buffer));
}

void V4L2VideoDecodeAccelerator::AssignPictureBuffers(
    const std::vector<media::PictureBuffer>& buffers) {
  DVLOG(3) << "AssignPictureBuffers(): buffer_count=" << buffers.size();
  DCHECK(child_message_loop_proxy_->BelongsToCurrentThread());

  if (buffers.size() != output_buffer_map_.size()) {
    DLOG(ERROR) << "AssignPictureBuffers(): Failed to provide requested picture"
                   " buffers. (Got " << buffers.size()
                << ", requested " << output_buffer_map_.size() << ")";
    NOTIFY_ERROR(INVALID_ARGUMENT);
    return;
  }

  if (!make_context_current_.Run()) {
    DLOG(ERROR) << "AssignPictureBuffers(): could not make context current";
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return;
  }

  gfx::ScopedTextureBinder bind_restore(GL_TEXTURE_EXTERNAL_OES, 0);
  EGLint attrs[] = {
      EGL_WIDTH,                     0, EGL_HEIGHT,                    0,
      EGL_LINUX_DRM_FOURCC_EXT,      0, EGL_DMA_BUF_PLANE0_FD_EXT,     0,
      EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, EGL_DMA_BUF_PLANE0_PITCH_EXT,  0,
      EGL_DMA_BUF_PLANE1_FD_EXT,     0, EGL_DMA_BUF_PLANE1_OFFSET_EXT, 0,
      EGL_DMA_BUF_PLANE1_PITCH_EXT,  0, EGL_NONE, };
  attrs[1] = frame_buffer_size_.width();
  attrs[3] = frame_buffer_size_.height();
  attrs[5] = DRM_FORMAT_NV12;

  // It's safe to manipulate all the buffer state here, because the decoder
  // thread is waiting on pictures_assigned_.
  DCHECK(free_output_buffers_.empty());
  for (size_t i = 0; i < output_buffer_map_.size(); ++i) {
    DCHECK(buffers[i].size() == frame_buffer_size_);

    OutputRecord& output_record = output_buffer_map_[i];
    DCHECK(!output_record.at_device);
    DCHECK(!output_record.at_client);
    DCHECK_EQ(output_record.egl_image, EGL_NO_IMAGE_KHR);
    DCHECK_EQ(output_record.egl_sync, EGL_NO_SYNC_KHR);
    DCHECK_EQ(output_record.picture_id, -1);
    DCHECK_EQ(output_record.cleared, false);

    attrs[7]  = output_record.fds[0];
    attrs[9]  = 0;
    attrs[11] = frame_buffer_size_.width();
    attrs[13] = output_record.fds[1];
    attrs[15] = 0;
    attrs[17] = frame_buffer_size_.width();

    EGLImageKHR egl_image = eglCreateImageKHR(
        egl_display_, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attrs);
    if (egl_image == EGL_NO_IMAGE_KHR) {
      DLOG(ERROR) << "AssignPictureBuffers(): could not create EGLImageKHR";
      // Ownership of EGLImages allocated in previous iterations of this loop
      // has been transferred to output_buffer_map_. After we error-out here
      // the destructor will handle their cleanup.
      NOTIFY_ERROR(PLATFORM_FAILURE);
      return;
    }

    glBindTexture(GL_TEXTURE_EXTERNAL_OES, buffers[i].texture_id());
    glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, egl_image);

    output_record.egl_image = egl_image;
    output_record.picture_id = buffers[i].id();
    free_output_buffers_.push(i);
    DVLOG(3) << "AssignPictureBuffers(): buffer[" << i
             << "]: picture_id=" << output_record.picture_id;
  }

  pictures_assigned_.Signal();
}

void V4L2VideoDecodeAccelerator::ReusePictureBuffer(int32 picture_buffer_id) {
  DVLOG(3) << "ReusePictureBuffer(): picture_buffer_id=" << picture_buffer_id;
  // Must be run on child thread, as we'll insert a sync in the EGL context.
  DCHECK(child_message_loop_proxy_->BelongsToCurrentThread());

  if (!make_context_current_.Run()) {
    DLOG(ERROR) << "ReusePictureBuffer(): could not make context current";
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return;
  }

  EGLSyncKHR egl_sync =
      eglCreateSyncKHR(egl_display_, EGL_SYNC_FENCE_KHR, NULL);
  if (egl_sync == EGL_NO_SYNC_KHR) {
    DLOG(ERROR) << "ReusePictureBuffer(): eglCreateSyncKHR() failed";
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return;
  }

  scoped_ptr<EGLSyncKHRRef> egl_sync_ref(new EGLSyncKHRRef(
      egl_display_, egl_sync));
  decoder_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
      &V4L2VideoDecodeAccelerator::ReusePictureBufferTask,
      base::Unretained(this), picture_buffer_id, base::Passed(&egl_sync_ref)));
}

void V4L2VideoDecodeAccelerator::Flush() {
  DVLOG(3) << "Flush()";
  DCHECK(child_message_loop_proxy_->BelongsToCurrentThread());
  decoder_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
      &V4L2VideoDecodeAccelerator::FlushTask, base::Unretained(this)));
}

void V4L2VideoDecodeAccelerator::Reset() {
  DVLOG(3) << "Reset()";
  DCHECK(child_message_loop_proxy_->BelongsToCurrentThread());
  decoder_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
      &V4L2VideoDecodeAccelerator::ResetTask, base::Unretained(this)));
}

void V4L2VideoDecodeAccelerator::Destroy() {
  DVLOG(3) << "Destroy()";
  DCHECK(child_message_loop_proxy_->BelongsToCurrentThread());

  // We're destroying; cancel all callbacks.
  client_ptr_factory_.InvalidateWeakPtrs();

  // If the decoder thread is running, destroy using posted task.
  if (decoder_thread_.IsRunning()) {
    decoder_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
        &V4L2VideoDecodeAccelerator::DestroyTask, base::Unretained(this)));
    pictures_assigned_.Signal();
    // DestroyTask() will cause the decoder_thread_ to flush all tasks.
    decoder_thread_.Stop();
  } else {
    // Otherwise, call the destroy task directly.
    DestroyTask();
  }

  // Set to kError state just in case.
  SetDecoderState(kError);

  delete this;
}

bool V4L2VideoDecodeAccelerator::CanDecodeOnIOThread() { return true; }

void V4L2VideoDecodeAccelerator::DecodeTask(
    const media::BitstreamBuffer& bitstream_buffer) {
  DVLOG(3) << "DecodeTask(): input_id=" << bitstream_buffer.id();
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  DCHECK_NE(decoder_state_, kUninitialized);
  TRACE_EVENT1("Video Decoder", "V4L2VDA::DecodeTask", "input_id",
               bitstream_buffer.id());

  scoped_ptr<BitstreamBufferRef> bitstream_record(new BitstreamBufferRef(
      io_client_, io_message_loop_proxy_,
      new base::SharedMemory(bitstream_buffer.handle(), true),
      bitstream_buffer.size(), bitstream_buffer.id()));
  if (!bitstream_record->shm->Map(bitstream_buffer.size())) {
    DLOG(ERROR) << "Decode(): could not map bitstream_buffer";
    NOTIFY_ERROR(UNREADABLE_INPUT);
    return;
  }
  DVLOG(3) << "DecodeTask(): mapped at=" << bitstream_record->shm->memory();

  if (decoder_state_ == kResetting || decoder_flushing_) {
    // In the case that we're resetting or flushing, we need to delay decoding
    // the BitstreamBuffers that come after the Reset() or Flush() call.  When
    // we're here, we know that this DecodeTask() was scheduled by a Decode()
    // call that came after (in the client thread) the Reset() or Flush() call;
    // thus set up the delay if necessary.
    if (decoder_delay_bitstream_buffer_id_ == -1)
      decoder_delay_bitstream_buffer_id_ = bitstream_record->input_id;
  } else if (decoder_state_ == kError) {
    DVLOG(2) << "DecodeTask(): early out: kError state";
    return;
  }

  decoder_input_queue_.push(
      linked_ptr<BitstreamBufferRef>(bitstream_record.release()));
  decoder_decode_buffer_tasks_scheduled_++;
  DecodeBufferTask();
}

void V4L2VideoDecodeAccelerator::DecodeBufferTask() {
  DVLOG(3) << "DecodeBufferTask()";
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  DCHECK_NE(decoder_state_, kUninitialized);
  TRACE_EVENT0("Video Decoder", "V4L2VDA::DecodeBufferTask");

  decoder_decode_buffer_tasks_scheduled_--;

  if (decoder_state_ == kResetting) {
    DVLOG(2) << "DecodeBufferTask(): early out: kResetting state";
    return;
  } else if (decoder_state_ == kError) {
    DVLOG(2) << "DecodeBufferTask(): early out: kError state";
    return;
  } else if (decoder_state_ == kChangingResolution) {
    DVLOG(2) << "DecodeBufferTask(): early out: resolution change pending";
    return;
  }

  if (decoder_current_bitstream_buffer_ == NULL) {
    if (decoder_input_queue_.empty()) {
      // We're waiting for a new buffer -- exit without scheduling a new task.
      return;
    }
    linked_ptr<BitstreamBufferRef>& buffer_ref = decoder_input_queue_.front();
    if (decoder_delay_bitstream_buffer_id_ == buffer_ref->input_id) {
      // We're asked to delay decoding on this and subsequent buffers.
      return;
    }

    // Setup to use the next buffer.
    decoder_current_bitstream_buffer_.reset(buffer_ref.release());
    decoder_input_queue_.pop();
    DVLOG(3) << "DecodeBufferTask(): reading input_id="
             << decoder_current_bitstream_buffer_->input_id
             << ", addr=" << (decoder_current_bitstream_buffer_->shm ?
                              decoder_current_bitstream_buffer_->shm->memory() :
                              NULL)
             << ", size=" << decoder_current_bitstream_buffer_->size;
  }
  bool schedule_task = false;
  const size_t size = decoder_current_bitstream_buffer_->size;
  size_t decoded_size = 0;
  if (size == 0) {
    const int32 input_id = decoder_current_bitstream_buffer_->input_id;
    if (input_id >= 0) {
      // This is a buffer queued from the client that has zero size.  Skip.
      schedule_task = true;
    } else {
      // This is a buffer of zero size, queued to flush the pipe.  Flush.
      DCHECK_EQ(decoder_current_bitstream_buffer_->shm.get(),
                static_cast<base::SharedMemory*>(NULL));
      // Enqueue a buffer guaranteed to be empty.  To do that, we flush the
      // current input, enqueue no data to the next frame, then flush that down.
      schedule_task = true;
      if (decoder_current_input_buffer_ != -1 &&
          input_buffer_map_[decoder_current_input_buffer_].input_id !=
              kFlushBufferId)
        schedule_task = FlushInputFrame();

      if (schedule_task && AppendToInputFrame(NULL, 0) && FlushInputFrame()) {
        DVLOG(2) << "DecodeBufferTask(): enqueued flush buffer";
        decoder_partial_frame_pending_ = false;
        schedule_task = true;
      } else {
        // If we failed to enqueue the empty buffer (due to pipeline
        // backpressure), don't advance the bitstream buffer queue, and don't
        // schedule the next task.  This bitstream buffer queue entry will get
        // reprocessed when the pipeline frees up.
        schedule_task = false;
      }
    }
  } else {
    // This is a buffer queued from the client, with actual contents.  Decode.
    const uint8* const data =
        reinterpret_cast<const uint8*>(
            decoder_current_bitstream_buffer_->shm->memory()) +
        decoder_current_bitstream_buffer_->bytes_used;
    const size_t data_size =
        decoder_current_bitstream_buffer_->size -
        decoder_current_bitstream_buffer_->bytes_used;
    if (!AdvanceFrameFragment(data, data_size, &decoded_size)) {
      NOTIFY_ERROR(UNREADABLE_INPUT);
      return;
    }
    // AdvanceFrameFragment should not return a size larger than the buffer
    // size, even on invalid data.
    CHECK_LE(decoded_size, data_size);

    switch (decoder_state_) {
      case kInitialized:
      case kAfterReset:
        schedule_task = DecodeBufferInitial(data, decoded_size, &decoded_size);
        break;
      case kDecoding:
        schedule_task = DecodeBufferContinue(data, decoded_size);
        break;
      default:
        NOTIFY_ERROR(ILLEGAL_STATE);
        return;
    }
  }
  if (decoder_state_ == kError) {
    // Failed during decode.
    return;
  }

  if (schedule_task) {
    decoder_current_bitstream_buffer_->bytes_used += decoded_size;
    if (decoder_current_bitstream_buffer_->bytes_used ==
        decoder_current_bitstream_buffer_->size) {
      // Our current bitstream buffer is done; return it.
      int32 input_id = decoder_current_bitstream_buffer_->input_id;
      DVLOG(3) << "DecodeBufferTask(): finished input_id=" << input_id;
      // BitstreamBufferRef destructor calls NotifyEndOfBitstreamBuffer().
      decoder_current_bitstream_buffer_.reset();
    }
    ScheduleDecodeBufferTaskIfNeeded();
  }
}

bool V4L2VideoDecodeAccelerator::AdvanceFrameFragment(
    const uint8* data,
    size_t size,
    size_t* endpos) {
  if (video_profile_ >= media::H264PROFILE_MIN &&
      video_profile_ <= media::H264PROFILE_MAX) {
    // For H264, we need to feed HW one frame at a time.  This is going to take
    // some parsing of our input stream.
    decoder_h264_parser_->SetStream(data, size);
    media::H264NALU nalu;
    media::H264Parser::Result result;
    *endpos = 0;

    // Keep on peeking the next NALs while they don't indicate a frame
    // boundary.
    for (;;) {
      bool end_of_frame = false;
      result = decoder_h264_parser_->AdvanceToNextNALU(&nalu);
      if (result == media::H264Parser::kInvalidStream ||
          result == media::H264Parser::kUnsupportedStream)
        return false;
      if (result == media::H264Parser::kEOStream) {
        // We've reached the end of the buffer before finding a frame boundary.
        decoder_partial_frame_pending_ = true;
        return true;
      }
      switch (nalu.nal_unit_type) {
        case media::H264NALU::kNonIDRSlice:
        case media::H264NALU::kIDRSlice:
          if (nalu.size < 1)
            return false;
          // For these two, if the "first_mb_in_slice" field is zero, start a
          // new frame and return.  This field is Exp-Golomb coded starting on
          // the eighth data bit of the NAL; a zero value is encoded with a
          // leading '1' bit in the byte, which we can detect as the byte being
          // (unsigned) greater than or equal to 0x80.
          if (nalu.data[1] >= 0x80) {
            end_of_frame = true;
            break;
          }
          break;
        case media::H264NALU::kSPS:
        case media::H264NALU::kPPS:
        case media::H264NALU::kEOSeq:
        case media::H264NALU::kEOStream:
          // These unconditionally signal a frame boundary.
          end_of_frame = true;
          break;
        default:
          // For all others, keep going.
          break;
      }
      if (end_of_frame) {
        if (!decoder_partial_frame_pending_ && *endpos == 0) {
          // The frame was previously restarted, and we haven't filled the
          // current frame with any contents yet.  Start the new frame here and
          // continue parsing NALs.
        } else {
          // The frame wasn't previously restarted and/or we have contents for
          // the current frame; signal the start of a new frame here: we don't
          // have a partial frame anymore.
          decoder_partial_frame_pending_ = false;
          return true;
        }
      }
      *endpos = (nalu.data + nalu.size) - data;
    }
    NOTREACHED();
    return false;
  } else {
    DCHECK_GE(video_profile_, media::VP8PROFILE_MIN);
    DCHECK_LE(video_profile_, media::VP8PROFILE_MAX);
    // For VP8, we can just dump the entire buffer.  No fragmentation needed,
    // and we never return a partial frame.
    *endpos = size;
    decoder_partial_frame_pending_ = false;
    return true;
  }
}

void V4L2VideoDecodeAccelerator::ScheduleDecodeBufferTaskIfNeeded() {
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());

  // If we're behind on tasks, schedule another one.
  int buffers_to_decode = decoder_input_queue_.size();
  if (decoder_current_bitstream_buffer_ != NULL)
    buffers_to_decode++;
  if (decoder_decode_buffer_tasks_scheduled_ < buffers_to_decode) {
    decoder_decode_buffer_tasks_scheduled_++;
    decoder_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
        &V4L2VideoDecodeAccelerator::DecodeBufferTask,
        base::Unretained(this)));
  }
}

bool V4L2VideoDecodeAccelerator::DecodeBufferInitial(
    const void* data, size_t size, size_t* endpos) {
  DVLOG(3) << "DecodeBufferInitial(): data=" << data << ", size=" << size;
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  DCHECK_NE(decoder_state_, kUninitialized);
  DCHECK_NE(decoder_state_, kDecoding);
  DCHECK(!device_poll_thread_.IsRunning());
  // Initial decode.  We haven't been able to get output stream format info yet.
  // Get it, and start decoding.

  // Copy in and send to HW.
  if (!AppendToInputFrame(data, size))
    return false;

  // If we only have a partial frame, don't flush and process yet.
  if (decoder_partial_frame_pending_)
    return true;

  if (!FlushInputFrame())
    return false;

  // Recycle buffers.
  Dequeue();

  // Check and see if we have format info yet.
  struct v4l2_format format;
  bool again = false;
  if (!GetFormatInfo(&format, &again))
    return false;

  if (again) {
    // Need more stream to decode format, return true and schedule next buffer.
    *endpos = size;
    return true;
  }

  // Run this initialization only on first startup.
  if (decoder_state_ == kInitialized) {
    DVLOG(3) << "DecodeBufferInitial(): running initialization";
    // Success! Setup our parameters.
    if (!CreateBuffersForFormat(format))
      return false;

    // We expect to process the initial buffer once during stream init to
    // configure stream parameters, but will not consume the steam data on that
    // iteration.  Subsequent iterations (including after reset) do not require
    // the stream init step.
    *endpos = 0;
  } else {
    *endpos = size;
  }

  // StartDevicePoll will raise the error if there is one.
  if (!StartDevicePoll())
    return false;

  decoder_state_ = kDecoding;
  ScheduleDecodeBufferTaskIfNeeded();
  return true;
}

bool V4L2VideoDecodeAccelerator::DecodeBufferContinue(
    const void* data, size_t size) {
  DVLOG(3) << "DecodeBufferContinue(): data=" << data << ", size=" << size;
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  DCHECK_EQ(decoder_state_, kDecoding);

  // Both of these calls will set kError state if they fail.
  // Only flush the frame if it's complete.
  return (AppendToInputFrame(data, size) &&
          (decoder_partial_frame_pending_ || FlushInputFrame()));
}

bool V4L2VideoDecodeAccelerator::AppendToInputFrame(
    const void* data, size_t size) {
  DVLOG(3) << "AppendToInputFrame()";
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  DCHECK_NE(decoder_state_, kUninitialized);
  DCHECK_NE(decoder_state_, kResetting);
  DCHECK_NE(decoder_state_, kError);
  // This routine can handle data == NULL and size == 0, which occurs when
  // we queue an empty buffer for the purposes of flushing the pipe.

  // Flush if we're too big
  if (decoder_current_input_buffer_ != -1) {
    InputRecord& input_record =
        input_buffer_map_[decoder_current_input_buffer_];
    if (input_record.bytes_used + size > input_record.length) {
      if (!FlushInputFrame())
        return false;
      decoder_current_input_buffer_ = -1;
    }
  }

  // Try to get an available input buffer
  if (decoder_current_input_buffer_ == -1) {
    if (free_input_buffers_.empty()) {
      // See if we can get more free buffers from HW
      Dequeue();
      if (free_input_buffers_.empty()) {
        // Nope!
        DVLOG(2) << "AppendToInputFrame(): stalled for input buffers";
        return false;
      }
    }
    decoder_current_input_buffer_ = free_input_buffers_.back();
    free_input_buffers_.pop_back();
    InputRecord& input_record =
        input_buffer_map_[decoder_current_input_buffer_];
    DCHECK_EQ(input_record.bytes_used, 0);
    DCHECK_EQ(input_record.input_id, -1);
    DCHECK(decoder_current_bitstream_buffer_ != NULL);
    input_record.input_id = decoder_current_bitstream_buffer_->input_id;
  }

  DCHECK(data != NULL || size == 0);
  if (size == 0) {
    // If we asked for an empty buffer, return now.  We return only after
    // getting the next input buffer, since we might actually want an empty
    // input buffer for flushing purposes.
    return true;
  }

  // Copy in to the buffer.
  InputRecord& input_record =
      input_buffer_map_[decoder_current_input_buffer_];
  if (size > input_record.length - input_record.bytes_used) {
    LOG(ERROR) << "AppendToInputFrame(): over-size frame, erroring";
    NOTIFY_ERROR(UNREADABLE_INPUT);
    return false;
  }
  memcpy(
      reinterpret_cast<uint8*>(input_record.address) + input_record.bytes_used,
      data,
      size);
  input_record.bytes_used += size;

  return true;
}

bool V4L2VideoDecodeAccelerator::FlushInputFrame() {
  DVLOG(3) << "FlushInputFrame()";
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  DCHECK_NE(decoder_state_, kUninitialized);
  DCHECK_NE(decoder_state_, kResetting);
  DCHECK_NE(decoder_state_, kError);

  if (decoder_current_input_buffer_ == -1)
    return true;

  InputRecord& input_record =
      input_buffer_map_[decoder_current_input_buffer_];
  DCHECK_NE(input_record.input_id, -1);
  DCHECK(input_record.input_id != kFlushBufferId ||
         input_record.bytes_used == 0);
  // * if input_id >= 0, this input buffer was prompted by a bitstream buffer we
  //   got from the client.  We can skip it if it is empty.
  // * if input_id < 0 (should be kFlushBufferId in this case), this input
  //   buffer was prompted by a flush buffer, and should be queued even when
  //   empty.
  if (input_record.input_id >= 0 && input_record.bytes_used == 0) {
    input_record.input_id = -1;
    free_input_buffers_.push_back(decoder_current_input_buffer_);
    decoder_current_input_buffer_ = -1;
    return true;
  }

  // Queue it.
  input_ready_queue_.push(decoder_current_input_buffer_);
  decoder_current_input_buffer_ = -1;
  DVLOG(3) << "FlushInputFrame(): submitting input_id="
           << input_record.input_id;
  // Enqueue once since there's new available input for it.
  Enqueue();

  return (decoder_state_ != kError);
}

void V4L2VideoDecodeAccelerator::ServiceDeviceTask(bool event_pending) {
  DVLOG(3) << "ServiceDeviceTask()";
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  DCHECK_NE(decoder_state_, kUninitialized);
  DCHECK_NE(decoder_state_, kInitialized);
  DCHECK_NE(decoder_state_, kAfterReset);
  TRACE_EVENT0("Video Decoder", "V4L2VDA::ServiceDeviceTask");

  if (decoder_state_ == kResetting) {
    DVLOG(2) << "ServiceDeviceTask(): early out: kResetting state";
    return;
  } else if (decoder_state_ == kError) {
    DVLOG(2) << "ServiceDeviceTask(): early out: kError state";
    return;
  } else if (decoder_state_ == kChangingResolution) {
    DVLOG(2) << "ServiceDeviceTask(): early out: kChangingResolution state";
    return;
  }

  if (event_pending)
    DequeueEvents();
  Dequeue();
  Enqueue();

  // Clear the interrupt fd.
  if (!device_->ClearDevicePollInterrupt()) {
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return;
  }

  bool poll_device = false;
  // Add fd, if we should poll on it.
  // Can be polled as soon as either input or output buffers are queued.
  if (input_buffer_queued_count_ + output_buffer_queued_count_ > 0)
    poll_device = true;

  // ServiceDeviceTask() should only ever be scheduled from DevicePollTask(),
  // so either:
  // * device_poll_thread_ is running normally
  // * device_poll_thread_ scheduled us, but then a ResetTask() or DestroyTask()
  //   shut it down, in which case we're either in kResetting or kError states
  //   respectively, and we should have early-outed already.
  DCHECK(device_poll_thread_.message_loop());
  // Queue the DevicePollTask() now.
  device_poll_thread_.message_loop()->PostTask(
      FROM_HERE,
      base::Bind(&V4L2VideoDecodeAccelerator::DevicePollTask,
                 base::Unretained(this),
                 poll_device));

  DVLOG(1) << "ServiceDeviceTask(): buffer counts: DEC["
           << decoder_input_queue_.size() << "->"
           << input_ready_queue_.size() << "] => DEVICE["
           << free_input_buffers_.size() << "+"
           << input_buffer_queued_count_ << "/"
           << input_buffer_map_.size() << "->"
           << free_output_buffers_.size() << "+"
           << output_buffer_queued_count_ << "/"
           << output_buffer_map_.size() << "] => VDA["
           << decoder_frames_at_client_ << "]";

  ScheduleDecodeBufferTaskIfNeeded();
  StartResolutionChangeIfNeeded();
}

void V4L2VideoDecodeAccelerator::Enqueue() {
  DVLOG(3) << "Enqueue()";
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  DCHECK_NE(decoder_state_, kUninitialized);
  TRACE_EVENT0("Video Decoder", "V4L2VDA::Enqueue");

  // Drain the pipe of completed decode buffers.
  const int old_inputs_queued = input_buffer_queued_count_;
  while (!input_ready_queue_.empty()) {
    if (!EnqueueInputRecord())
      return;
  }
  if (old_inputs_queued == 0 && input_buffer_queued_count_ != 0) {
    // We just started up a previously empty queue.
    // Queue state changed; signal interrupt.
    if (!device_->SetDevicePollInterrupt()) {
      DPLOG(ERROR) << "SetDevicePollInterrupt(): failed";
      NOTIFY_ERROR(PLATFORM_FAILURE);
      return;
    }
    // Start VIDIOC_STREAMON if we haven't yet.
    if (!input_streamon_) {
      __u32 type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
      IOCTL_OR_ERROR_RETURN(VIDIOC_STREAMON, &type);
      input_streamon_ = true;
    }
  }

  // Enqueue all the outputs we can.
  const int old_outputs_queued = output_buffer_queued_count_;
  while (!free_output_buffers_.empty()) {
    if (!EnqueueOutputRecord())
      return;
  }
  if (old_outputs_queued == 0 && output_buffer_queued_count_ != 0) {
    // We just started up a previously empty queue.
    // Queue state changed; signal interrupt.
    if (!device_->SetDevicePollInterrupt()) {
      DPLOG(ERROR) << "SetDevicePollInterrupt(): failed";
      NOTIFY_ERROR(PLATFORM_FAILURE);
      return;
    }
    // Start VIDIOC_STREAMON if we haven't yet.
    if (!output_streamon_) {
      __u32 type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
      IOCTL_OR_ERROR_RETURN(VIDIOC_STREAMON, &type);
      output_streamon_ = true;
    }
  }
}

void V4L2VideoDecodeAccelerator::DequeueEvents() {
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  DCHECK_NE(decoder_state_, kUninitialized);
  DVLOG(3) << "DequeueEvents()";

  struct v4l2_event ev;
  memset(&ev, 0, sizeof(ev));

  while (device_->Ioctl(VIDIOC_DQEVENT, &ev) == 0) {
    if (ev.type == V4L2_EVENT_RESOLUTION_CHANGE) {
      DVLOG(3) << "DequeueEvents(): got resolution change event.";
      DCHECK(!resolution_change_pending_);
      resolution_change_pending_ = true;
    } else {
      DLOG(FATAL) << "DequeueEvents(): got an event (" << ev.type
                  << ") we haven't subscribed to.";
    }
  }
}

void V4L2VideoDecodeAccelerator::Dequeue() {
  DVLOG(3) << "Dequeue()";
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  DCHECK_NE(decoder_state_, kUninitialized);
  TRACE_EVENT0("Video Decoder", "V4L2VDA::Dequeue");

  // Dequeue completed input (VIDEO_OUTPUT) buffers, and recycle to the free
  // list.
  struct v4l2_buffer dqbuf;
  struct v4l2_plane planes[2];
  while (input_buffer_queued_count_ > 0) {
    DCHECK(input_streamon_);
    memset(&dqbuf, 0, sizeof(dqbuf));
    memset(planes, 0, sizeof(planes));
    dqbuf.type   = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
    dqbuf.memory = V4L2_MEMORY_MMAP;
    dqbuf.m.planes = planes;
    dqbuf.length = 1;
    if (device_->Ioctl(VIDIOC_DQBUF, &dqbuf) != 0) {
      if (errno == EAGAIN) {
        // EAGAIN if we're just out of buffers to dequeue.
        break;
      }
      DPLOG(ERROR) << "Dequeue(): ioctl() failed: VIDIOC_DQBUF";
      NOTIFY_ERROR(PLATFORM_FAILURE);
      return;
    }
    InputRecord& input_record = input_buffer_map_[dqbuf.index];
    DCHECK(input_record.at_device);
    free_input_buffers_.push_back(dqbuf.index);
    input_record.at_device = false;
    input_record.bytes_used = 0;
    input_record.input_id = -1;
    input_buffer_queued_count_--;
  }

  // Dequeue completed output (VIDEO_CAPTURE) buffers, and queue to the
  // completed queue.
  while (output_buffer_queued_count_ > 0) {
    DCHECK(output_streamon_);
    memset(&dqbuf, 0, sizeof(dqbuf));
    memset(planes, 0, sizeof(planes));
    dqbuf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    dqbuf.memory = V4L2_MEMORY_MMAP;
    dqbuf.m.planes = planes;
    dqbuf.length = 2;
    if (device_->Ioctl(VIDIOC_DQBUF, &dqbuf) != 0) {
      if (errno == EAGAIN) {
        // EAGAIN if we're just out of buffers to dequeue.
        break;
      }
      DPLOG(ERROR) << "Dequeue(): ioctl() failed: VIDIOC_DQBUF";
      NOTIFY_ERROR(PLATFORM_FAILURE);
      return;
    }
    OutputRecord& output_record = output_buffer_map_[dqbuf.index];
    DCHECK(output_record.at_device);
    DCHECK(!output_record.at_client);
    DCHECK_NE(output_record.egl_image, EGL_NO_IMAGE_KHR);
    DCHECK_NE(output_record.picture_id, -1);
    output_record.at_device = false;
    if (dqbuf.m.planes[0].bytesused + dqbuf.m.planes[1].bytesused == 0) {
      // This is an empty output buffer returned as part of a flush.
      free_output_buffers_.push(dqbuf.index);
    } else {
      DCHECK_GE(dqbuf.timestamp.tv_sec, 0);
      output_record.at_client = true;
      DVLOG(3) << "Dequeue(): returning input_id=" << dqbuf.timestamp.tv_sec
               << " as picture_id=" << output_record.picture_id;
      const media::Picture& picture =
          media::Picture(output_record.picture_id, dqbuf.timestamp.tv_sec);
      pending_picture_ready_.push(
          PictureRecord(output_record.cleared, picture));
      SendPictureReady();
      output_record.cleared = true;
      decoder_frames_at_client_++;
    }
    output_buffer_queued_count_--;
  }

  NotifyFlushDoneIfNeeded();
}

bool V4L2VideoDecodeAccelerator::EnqueueInputRecord() {
  DVLOG(3) << "EnqueueInputRecord()";
  DCHECK(!input_ready_queue_.empty());

  // Enqueue an input (VIDEO_OUTPUT) buffer.
  const int buffer = input_ready_queue_.front();
  InputRecord& input_record = input_buffer_map_[buffer];
  DCHECK(!input_record.at_device);
  struct v4l2_buffer qbuf;
  struct v4l2_plane qbuf_plane;
  memset(&qbuf, 0, sizeof(qbuf));
  memset(&qbuf_plane, 0, sizeof(qbuf_plane));
  qbuf.index                 = buffer;
  qbuf.type                  = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
  qbuf.timestamp.tv_sec      = input_record.input_id;
  qbuf.memory                = V4L2_MEMORY_MMAP;
  qbuf.m.planes              = &qbuf_plane;
  qbuf.m.planes[0].bytesused = input_record.bytes_used;
  qbuf.length                = 1;
  IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QBUF, &qbuf);
  input_ready_queue_.pop();
  input_record.at_device = true;
  input_buffer_queued_count_++;
  DVLOG(3) << "EnqueueInputRecord(): enqueued input_id="
           << input_record.input_id << " size="  << input_record.bytes_used;
  return true;
}

bool V4L2VideoDecodeAccelerator::EnqueueOutputRecord() {
  DVLOG(3) << "EnqueueOutputRecord()";
  DCHECK(!free_output_buffers_.empty());

  // Enqueue an output (VIDEO_CAPTURE) buffer.
  const int buffer = free_output_buffers_.front();
  OutputRecord& output_record = output_buffer_map_[buffer];
  DCHECK(!output_record.at_device);
  DCHECK(!output_record.at_client);
  DCHECK_NE(output_record.egl_image, EGL_NO_IMAGE_KHR);
  DCHECK_NE(output_record.picture_id, -1);
  if (output_record.egl_sync != EGL_NO_SYNC_KHR) {
    TRACE_EVENT0("Video Decoder",
                 "V4L2VDA::EnqueueOutputRecord: eglClientWaitSyncKHR");
    // If we have to wait for completion, wait.  Note that
    // free_output_buffers_ is a FIFO queue, so we always wait on the
    // buffer that has been in the queue the longest.
    if (eglClientWaitSyncKHR(egl_display_, output_record.egl_sync, 0,
                             EGL_FOREVER_KHR) == EGL_FALSE) {
      // This will cause tearing, but is safe otherwise.
      DVLOG(1) << __func__ << " eglClientWaitSyncKHR failed!";
    }
    if (eglDestroySyncKHR(egl_display_, output_record.egl_sync) != EGL_TRUE) {
      DLOG(FATAL) << __func__ << " eglDestroySyncKHR failed!";
      NOTIFY_ERROR(PLATFORM_FAILURE);
      return false;
    }
    output_record.egl_sync = EGL_NO_SYNC_KHR;
  }
  struct v4l2_buffer qbuf;
  struct v4l2_plane qbuf_planes[arraysize(output_record.fds)];
  memset(&qbuf, 0, sizeof(qbuf));
  memset(qbuf_planes, 0, sizeof(qbuf_planes));
  qbuf.index    = buffer;
  qbuf.type     = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
  qbuf.memory   = V4L2_MEMORY_MMAP;
  qbuf.m.planes = qbuf_planes;
  qbuf.length   = arraysize(output_record.fds);
  IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QBUF, &qbuf);
  free_output_buffers_.pop();
  output_record.at_device = true;
  output_buffer_queued_count_++;
  return true;
}

void V4L2VideoDecodeAccelerator::ReusePictureBufferTask(
    int32 picture_buffer_id, scoped_ptr<EGLSyncKHRRef> egl_sync_ref) {
  DVLOG(3) << "ReusePictureBufferTask(): picture_buffer_id="
           << picture_buffer_id;
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  TRACE_EVENT0("Video Decoder", "V4L2VDA::ReusePictureBufferTask");

  // We run ReusePictureBufferTask even if we're in kResetting.
  if (decoder_state_ == kError) {
    DVLOG(2) << "ReusePictureBufferTask(): early out: kError state";
    return;
  }

  if (decoder_state_ == kChangingResolution) {
    DVLOG(2) << "ReusePictureBufferTask(): early out: kChangingResolution";
    return;
  }

  size_t index;
  for (index = 0; index < output_buffer_map_.size(); ++index)
    if (output_buffer_map_[index].picture_id == picture_buffer_id)
      break;

  if (index >= output_buffer_map_.size()) {
    // It's possible that we've already posted a DismissPictureBuffer for this
    // picture, but it has not yet executed when this ReusePictureBuffer was
    // posted to us by the client. In that case just ignore this (we've already
    // dismissed it and accounted for that) and let the sync object get
    // destroyed.
    DVLOG(4) << "ReusePictureBufferTask(): got picture id= "
             << picture_buffer_id << " not in use (anymore?).";
    return;
  }

  OutputRecord& output_record = output_buffer_map_[index];
  if (output_record.at_device || !output_record.at_client) {
    DLOG(ERROR) << "ReusePictureBufferTask(): picture_buffer_id not reusable";
    NOTIFY_ERROR(INVALID_ARGUMENT);
    return;
  }

  DCHECK_EQ(output_record.egl_sync, EGL_NO_SYNC_KHR);
  DCHECK(!output_record.at_device);
  output_record.at_client = false;
  output_record.egl_sync = egl_sync_ref->egl_sync;
  free_output_buffers_.push(index);
  decoder_frames_at_client_--;
  // Take ownership of the EGLSync.
  egl_sync_ref->egl_sync = EGL_NO_SYNC_KHR;
  // We got a buffer back, so enqueue it back.
  Enqueue();
}

void V4L2VideoDecodeAccelerator::FlushTask() {
  DVLOG(3) << "FlushTask()";
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  TRACE_EVENT0("Video Decoder", "V4L2VDA::FlushTask");

  // Flush outstanding buffers.
  if (decoder_state_ == kInitialized || decoder_state_ == kAfterReset) {
    // There's nothing in the pipe, so return done immediately.
    DVLOG(3) << "FlushTask(): returning flush";
    child_message_loop_proxy_->PostTask(
        FROM_HERE, base::Bind(&Client::NotifyFlushDone, client_));
    return;
  } else if (decoder_state_ == kError) {
    DVLOG(2) << "FlushTask(): early out: kError state";
    return;
  }

  // We don't support stacked flushing.
  DCHECK(!decoder_flushing_);

  // Queue up an empty buffer -- this triggers the flush.
  decoder_input_queue_.push(
      linked_ptr<BitstreamBufferRef>(new BitstreamBufferRef(
          io_client_, io_message_loop_proxy_, NULL, 0, kFlushBufferId)));
  decoder_flushing_ = true;
  SendPictureReady();  // Send all pending PictureReady.

  ScheduleDecodeBufferTaskIfNeeded();
}

void V4L2VideoDecodeAccelerator::NotifyFlushDoneIfNeeded() {
  if (!decoder_flushing_)
    return;

  // Pipeline is empty when:
  // * Decoder input queue is empty of non-delayed buffers.
  // * There is no currently filling input buffer.
  // * Input holding queue is empty.
  // * All input (VIDEO_OUTPUT) buffers are returned.
  if (!decoder_input_queue_.empty()) {
    if (decoder_input_queue_.front()->input_id !=
        decoder_delay_bitstream_buffer_id_)
      return;
  }
  if (decoder_current_input_buffer_ != -1)
    return;
  if ((input_ready_queue_.size() + input_buffer_queued_count_) != 0)
    return;

  // TODO(posciak): crbug.com/270039. Exynos requires a streamoff-streamon
  // sequence after flush to continue, even if we are not resetting. This would
  // make sense, because we don't really want to resume from a non-resume point
  // (e.g. not from an IDR) if we are flushed.
  // MSE player however triggers a Flush() on chunk end, but never Reset(). One
  // could argue either way, or even say that Flush() is not needed/harmful when
  // transitioning to next chunk.
  // For now, do the streamoff-streamon cycle to satisfy Exynos and not freeze
  // when doing MSE. This should be harmless otherwise.
  if (!StopDevicePoll(false))
    return;

  if (!StartDevicePoll())
    return;

  decoder_delay_bitstream_buffer_id_ = -1;
  decoder_flushing_ = false;
  DVLOG(3) << "NotifyFlushDoneIfNeeded(): returning flush";
  child_message_loop_proxy_->PostTask(
      FROM_HERE, base::Bind(&Client::NotifyFlushDone, client_));

  // While we were flushing, we early-outed DecodeBufferTask()s.
  ScheduleDecodeBufferTaskIfNeeded();
}

void V4L2VideoDecodeAccelerator::ResetTask() {
  DVLOG(3) << "ResetTask()";
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  TRACE_EVENT0("Video Decoder", "V4L2VDA::ResetTask");

  if (decoder_state_ == kError) {
    DVLOG(2) << "ResetTask(): early out: kError state";
    return;
  }

  // If we are in the middle of switching resolutions, postpone reset until
  // it's done. We don't have to worry about timing of this wrt to decoding,
  // because input pipe is already stopped if we are changing resolution.
  // We will come back here after we are done with the resolution change.
  DCHECK(!resolution_change_reset_pending_);
  if (resolution_change_pending_ || decoder_state_ == kChangingResolution) {
    resolution_change_reset_pending_ = true;
    return;
  }

  // We stop streaming and clear buffer tracking info (not preserving inputs).
  // StopDevicePoll() unconditionally does _not_ destroy buffers, however.
  if (!StopDevicePoll(false))
    return;

  decoder_current_bitstream_buffer_.reset();
  while (!decoder_input_queue_.empty())
    decoder_input_queue_.pop();

  decoder_current_input_buffer_ = -1;

  // If we were flushing, we'll never return any more BitstreamBuffers or
  // PictureBuffers; they have all been dropped and returned by now.
  NotifyFlushDoneIfNeeded();

  // Mark that we're resetting, then enqueue a ResetDoneTask().  All intervening
  // jobs will early-out in the kResetting state.
  decoder_state_ = kResetting;
  SendPictureReady();  // Send all pending PictureReady.
  decoder_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
      &V4L2VideoDecodeAccelerator::ResetDoneTask, base::Unretained(this)));
}

void V4L2VideoDecodeAccelerator::ResetDoneTask() {
  DVLOG(3) << "ResetDoneTask()";
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  TRACE_EVENT0("Video Decoder", "V4L2VDA::ResetDoneTask");

  if (decoder_state_ == kError) {
    DVLOG(2) << "ResetDoneTask(): early out: kError state";
    return;
  }

  // We might have received a resolution change event while we were waiting
  // for the reset to finish. The codec will not post another event if the
  // resolution after reset remains the same as the one to which were just
  // about to switch, so preserve the event across reset so we can address
  // it after resuming.

  // Reset format-specific bits.
  if (video_profile_ >= media::H264PROFILE_MIN &&
      video_profile_ <= media::H264PROFILE_MAX) {
    decoder_h264_parser_.reset(new media::H264Parser());
  }

  // Jobs drained, we're finished resetting.
  DCHECK_EQ(decoder_state_, kResetting);
  if (output_buffer_map_.empty()) {
    // We must have gotten Reset() before we had a chance to request buffers
    // from the client.
    decoder_state_ = kInitialized;
  } else {
    decoder_state_ = kAfterReset;
  }

  decoder_partial_frame_pending_ = false;
  decoder_delay_bitstream_buffer_id_ = -1;
  child_message_loop_proxy_->PostTask(FROM_HERE, base::Bind(
      &Client::NotifyResetDone, client_));

  // While we were resetting, we early-outed DecodeBufferTask()s.
  ScheduleDecodeBufferTaskIfNeeded();
}

void V4L2VideoDecodeAccelerator::DestroyTask() {
  DVLOG(3) << "DestroyTask()";
  TRACE_EVENT0("Video Decoder", "V4L2VDA::DestroyTask");

  // DestroyTask() should run regardless of decoder_state_.

  // Stop streaming and the device_poll_thread_.
  StopDevicePoll(false);

  decoder_current_bitstream_buffer_.reset();
  decoder_current_input_buffer_ = -1;
  decoder_decode_buffer_tasks_scheduled_ = 0;
  decoder_frames_at_client_ = 0;
  while (!decoder_input_queue_.empty())
    decoder_input_queue_.pop();
  decoder_flushing_ = false;

  // Set our state to kError.  Just in case.
  decoder_state_ = kError;
}

bool V4L2VideoDecodeAccelerator::StartDevicePoll() {
  DVLOG(3) << "StartDevicePoll()";
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  DCHECK(!device_poll_thread_.IsRunning());

  // Start up the device poll thread and schedule its first DevicePollTask().
  if (!device_poll_thread_.Start()) {
    DLOG(ERROR) << "StartDevicePoll(): Device thread failed to start";
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return false;
  }
  device_poll_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
      &V4L2VideoDecodeAccelerator::DevicePollTask,
      base::Unretained(this),
      0));

  return true;
}

bool V4L2VideoDecodeAccelerator::StopDevicePoll(bool keep_input_state) {
  DVLOG(3) << "StopDevicePoll()";
  if (decoder_thread_.IsRunning())
    DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());

  // Signal the DevicePollTask() to stop, and stop the device poll thread.
  if (!device_->SetDevicePollInterrupt()) {
    DPLOG(ERROR) << "SetDevicePollInterrupt(): failed";
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return false;
  }
  device_poll_thread_.Stop();
  // Clear the interrupt now, to be sure.
  if (!device_->ClearDevicePollInterrupt()) {
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return false;
  }

  // Stop streaming.
  if (!keep_input_state) {
    if (input_streamon_) {
      __u32 type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
      IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_STREAMOFF, &type);
    }
    input_streamon_ = false;
  }
  if (output_streamon_) {
    __u32 type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_STREAMOFF, &type);
  }
  output_streamon_ = false;

  // Reset all our accounting info.
  if (!keep_input_state) {
    while (!input_ready_queue_.empty())
      input_ready_queue_.pop();
    free_input_buffers_.clear();
    for (size_t i = 0; i < input_buffer_map_.size(); ++i) {
      free_input_buffers_.push_back(i);
      input_buffer_map_[i].at_device = false;
      input_buffer_map_[i].bytes_used = 0;
      input_buffer_map_[i].input_id = -1;
    }
    input_buffer_queued_count_ = 0;
  }

  while (!free_output_buffers_.empty())
    free_output_buffers_.pop();

  for (size_t i = 0; i < output_buffer_map_.size(); ++i) {
    OutputRecord& output_record = output_buffer_map_[i];
    DCHECK(!(output_record.at_client && output_record.at_device));

    // After streamoff, the device drops ownership of all buffers, even if
    // we don't dequeue them explicitly.
    output_buffer_map_[i].at_device = false;
    // Some of them may still be owned by the client however.
    // Reuse only those that aren't.
    if (!output_record.at_client) {
      DCHECK_EQ(output_record.egl_sync, EGL_NO_SYNC_KHR);
      free_output_buffers_.push(i);
    }
  }
  output_buffer_queued_count_ = 0;

  DVLOG(3) << "StopDevicePoll(): device poll stopped";
  return true;
}

void V4L2VideoDecodeAccelerator::StartResolutionChangeIfNeeded() {
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  DCHECK_EQ(decoder_state_, kDecoding);

  if (!resolution_change_pending_)
    return;

  DVLOG(3) << "No more work, initiate resolution change";

  // Keep input queue.
  if (!StopDevicePoll(true))
    return;

  decoder_state_ = kChangingResolution;
  DCHECK(resolution_change_pending_);
  resolution_change_pending_ = false;

  // Post a task to clean up buffers on child thread. This will also ensure
  // that we won't accept ReusePictureBuffer() anymore after that.
  child_message_loop_proxy_->PostTask(FROM_HERE, base::Bind(
      &V4L2VideoDecodeAccelerator::ResolutionChangeDestroyBuffers,
      weak_this_));
}

void V4L2VideoDecodeAccelerator::FinishResolutionChange() {
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  DCHECK_EQ(decoder_state_, kChangingResolution);
  DVLOG(3) << "FinishResolutionChange()";

  if (decoder_state_ == kError) {
    DVLOG(2) << "FinishResolutionChange(): early out: kError state";
    return;
  }

  struct v4l2_format format;
  bool again;
  bool ret = GetFormatInfo(&format, &again);
  if (!ret || again) {
    DVLOG(3) << "Couldn't get format information after resolution change";
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return;
  }

  if (!CreateBuffersForFormat(format)) {
    DVLOG(3) << "Couldn't reallocate buffers after resolution change";
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return;
  }

  decoder_state_ = kDecoding;

  if (resolution_change_reset_pending_) {
    resolution_change_reset_pending_ = false;
    ResetTask();
    return;
  }

  if (!StartDevicePoll())
    return;

  Enqueue();
  ScheduleDecodeBufferTaskIfNeeded();
}

void V4L2VideoDecodeAccelerator::DevicePollTask(bool poll_device) {
  DVLOG(3) << "DevicePollTask()";
  DCHECK_EQ(device_poll_thread_.message_loop(), base::MessageLoop::current());
  TRACE_EVENT0("Video Decoder", "V4L2VDA::DevicePollTask");

  bool event_pending = false;

  if (!device_->Poll(poll_device, &event_pending)) {
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return;
  }

  // All processing should happen on ServiceDeviceTask(), since we shouldn't
  // touch decoder state from this thread.
  decoder_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
      &V4L2VideoDecodeAccelerator::ServiceDeviceTask,
      base::Unretained(this), event_pending));
}

void V4L2VideoDecodeAccelerator::NotifyError(Error error) {
  DVLOG(2) << "NotifyError()";

  if (!child_message_loop_proxy_->BelongsToCurrentThread()) {
    child_message_loop_proxy_->PostTask(FROM_HERE, base::Bind(
        &V4L2VideoDecodeAccelerator::NotifyError, weak_this_, error));
    return;
  }

  if (client_) {
    client_->NotifyError(error);
    client_ptr_factory_.InvalidateWeakPtrs();
  }
}

void V4L2VideoDecodeAccelerator::SetDecoderState(State state) {
  DVLOG(3) << "SetDecoderState(): state=" << state;

  // We can touch decoder_state_ only if this is the decoder thread or the
  // decoder thread isn't running.
  if (decoder_thread_.message_loop() != NULL &&
      decoder_thread_.message_loop() != base::MessageLoop::current()) {
    decoder_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
        &V4L2VideoDecodeAccelerator::SetDecoderState,
        base::Unretained(this), state));
  } else {
    decoder_state_ = state;
  }
}

bool V4L2VideoDecodeAccelerator::GetFormatInfo(struct v4l2_format* format,
                                                 bool* again) {
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());

  *again = false;
  memset(format, 0, sizeof(*format));
  format->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
  if (HANDLE_EINTR(device_->Ioctl(VIDIOC_G_FMT, format)) != 0) {
    if (errno == EINVAL) {
      // EINVAL means we haven't seen sufficient stream to decode the format.
      *again = true;
      return true;
    } else {
      DPLOG(ERROR) << __func__ << "(): ioctl() failed: VIDIOC_G_FMT";
      NOTIFY_ERROR(PLATFORM_FAILURE);
      return false;
    }
  }

  return true;
}

bool V4L2VideoDecodeAccelerator::CreateBuffersForFormat(
    const struct v4l2_format& format) {
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  CHECK_EQ(format.fmt.pix_mp.num_planes, 2);
  frame_buffer_size_.SetSize(
      format.fmt.pix_mp.width, format.fmt.pix_mp.height);
  output_buffer_pixelformat_ = format.fmt.pix_mp.pixelformat;
  DCHECK_EQ(output_buffer_pixelformat_, V4L2_PIX_FMT_NV12M);
  DVLOG(3) << "CreateBuffersForFormat(): new resolution: "
           << frame_buffer_size_.ToString();

  if (!CreateOutputBuffers())
    return false;

  return true;
}

bool V4L2VideoDecodeAccelerator::CreateInputBuffers() {
  DVLOG(3) << "CreateInputBuffers()";
  // We always run this as we prepare to initialize.
  DCHECK_EQ(decoder_state_, kUninitialized);
  DCHECK(!input_streamon_);
  DCHECK(input_buffer_map_.empty());

  __u32 pixelformat = 0;
  if (video_profile_ >= media::H264PROFILE_MIN &&
      video_profile_ <= media::H264PROFILE_MAX) {
    pixelformat = V4L2_PIX_FMT_H264;
  } else if (video_profile_ >= media::VP8PROFILE_MIN &&
             video_profile_ <= media::VP8PROFILE_MAX) {
    pixelformat = V4L2_PIX_FMT_VP8;
  } else {
    NOTREACHED();
  }

  struct v4l2_format format;
  memset(&format, 0, sizeof(format));
  format.type                              = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
  format.fmt.pix_mp.pixelformat            = pixelformat;
  format.fmt.pix_mp.plane_fmt[0].sizeimage = kInputBufferMaxSize;
  format.fmt.pix_mp.num_planes             = 1;
  IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_FMT, &format);

  struct v4l2_requestbuffers reqbufs;
  memset(&reqbufs, 0, sizeof(reqbufs));
  reqbufs.count  = kInputBufferCount;
  reqbufs.type   = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
  reqbufs.memory = V4L2_MEMORY_MMAP;
  IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_REQBUFS, &reqbufs);
  input_buffer_map_.resize(reqbufs.count);
  for (size_t i = 0; i < input_buffer_map_.size(); ++i) {
    free_input_buffers_.push_back(i);

    // Query for the MEMORY_MMAP pointer.
    struct v4l2_plane planes[1];
    struct v4l2_buffer buffer;
    memset(&buffer, 0, sizeof(buffer));
    memset(planes, 0, sizeof(planes));
    buffer.index    = i;
    buffer.type     = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
    buffer.memory   = V4L2_MEMORY_MMAP;
    buffer.m.planes = planes;
    buffer.length   = 1;
    IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QUERYBUF, &buffer);
    void* address = device_->Mmap(NULL,
                                  buffer.m.planes[0].length,
                                  PROT_READ | PROT_WRITE,
                                  MAP_SHARED,
                                  buffer.m.planes[0].m.mem_offset);
    if (address == MAP_FAILED) {
      DPLOG(ERROR) << "CreateInputBuffers(): mmap() failed";
      return false;
    }
    input_buffer_map_[i].address = address;
    input_buffer_map_[i].length = buffer.m.planes[0].length;
  }

  return true;
}

bool V4L2VideoDecodeAccelerator::CreateOutputBuffers() {
  DVLOG(3) << "CreateOutputBuffers()";
  DCHECK(decoder_state_ == kInitialized ||
         decoder_state_ == kChangingResolution);
  DCHECK(!output_streamon_);
  DCHECK(output_buffer_map_.empty());

  // Number of output buffers we need.
  struct v4l2_control ctrl;
  memset(&ctrl, 0, sizeof(ctrl));
  ctrl.id = V4L2_CID_MIN_BUFFERS_FOR_CAPTURE;
  IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_G_CTRL, &ctrl);
  output_dpb_size_ = ctrl.value;

  // Output format setup in Initialize().

  // Allocate the output buffers.
  struct v4l2_requestbuffers reqbufs;
  memset(&reqbufs, 0, sizeof(reqbufs));
  reqbufs.count  = output_dpb_size_ + kDpbOutputBufferExtraCount;
  reqbufs.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
  reqbufs.memory = V4L2_MEMORY_MMAP;
  IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_REQBUFS, &reqbufs);

  // Create DMABUFs from output buffers.
  output_buffer_map_.resize(reqbufs.count);
  for (size_t i = 0; i < output_buffer_map_.size(); ++i) {
    OutputRecord& output_record = output_buffer_map_[i];
    for (size_t j = 0; j < arraysize(output_record.fds); ++j) {
      // Export the DMABUF fd so we can export it as a texture.
      struct v4l2_exportbuffer expbuf;
      memset(&expbuf, 0, sizeof(expbuf));
      expbuf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
      expbuf.index = i;
      expbuf.plane = j;
      expbuf.flags = O_CLOEXEC;
      IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_EXPBUF, &expbuf);
      output_record.fds[j] = expbuf.fd;
    }
  }

  DVLOG(3) << "CreateOutputBuffers(): ProvidePictureBuffers(): "
           << "buffer_count=" << output_buffer_map_.size()
           << ", width=" << frame_buffer_size_.width()
           << ", height=" << frame_buffer_size_.height();
  child_message_loop_proxy_->PostTask(FROM_HERE,
                                      base::Bind(&Client::ProvidePictureBuffers,
                                                 client_,
                                                 output_buffer_map_.size(),
                                                 frame_buffer_size_,
                                                 GL_TEXTURE_EXTERNAL_OES));

  // Wait for the client to call AssignPictureBuffers() on the Child thread.
  // We do this, because if we continue decoding without finishing buffer
  // allocation, we may end up Resetting before AssignPictureBuffers arrives,
  // resulting in unnecessary complications and subtle bugs.
  // For example, if the client calls Decode(Input1), Reset(), Decode(Input2)
  // in a sequence, and Decode(Input1) results in us getting here and exiting
  // without waiting, we might end up running Reset{,Done}Task() before
  // AssignPictureBuffers is scheduled, thus cleaning up and pushing buffers
  // to the free_output_buffers_ map twice. If we somehow marked buffers as
  // not ready, we'd need special handling for restarting the second Decode
  // task and delaying it anyway.
  // Waiting here is not very costly and makes reasoning about different
  // situations much simpler.
  pictures_assigned_.Wait();

  Enqueue();
  return true;
}

void V4L2VideoDecodeAccelerator::DestroyInputBuffers() {
  DVLOG(3) << "DestroyInputBuffers()";
  DCHECK(child_message_loop_proxy_->BelongsToCurrentThread());
  DCHECK(!input_streamon_);

  for (size_t i = 0; i < input_buffer_map_.size(); ++i) {
    if (input_buffer_map_[i].address != NULL) {
      device_->Munmap(input_buffer_map_[i].address,
                      input_buffer_map_[i].length);
    }
  }

  struct v4l2_requestbuffers reqbufs;
  memset(&reqbufs, 0, sizeof(reqbufs));
  reqbufs.count = 0;
  reqbufs.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
  reqbufs.memory = V4L2_MEMORY_MMAP;
  if (device_->Ioctl(VIDIOC_REQBUFS, &reqbufs) != 0)
    DPLOG(ERROR) << "DestroyInputBuffers(): ioctl() failed: VIDIOC_REQBUFS";

  input_buffer_map_.clear();
  free_input_buffers_.clear();
}

bool V4L2VideoDecodeAccelerator::DestroyOutputBuffers() {
  DVLOG(3) << "DestroyOutputBuffers()";
  DCHECK(child_message_loop_proxy_->BelongsToCurrentThread());
  DCHECK(!output_streamon_);
  bool success = true;

  for (size_t i = 0; i < output_buffer_map_.size(); ++i) {
    OutputRecord& output_record = output_buffer_map_[i];
    for (size_t j = 0; j < arraysize(output_record.fds); ++j) {
      if (output_record.fds[j] != -1) {
        if (close(output_record.fds[j])) {
          DVPLOG(1) << __func__ << " close() on a dmabuf fd failed.";
          success = false;
        }
      }
    }
    if (output_record.egl_image != EGL_NO_IMAGE_KHR) {
      if (eglDestroyImageKHR(egl_display_, output_record.egl_image) !=
          EGL_TRUE) {
        DVLOG(1) << __func__ << " eglDestroyImageKHR failed.";
        success = false;
      }
    }

    if (output_record.egl_sync != EGL_NO_SYNC_KHR) {
      if (eglDestroySyncKHR(egl_display_, output_record.egl_sync) != EGL_TRUE) {
        DVLOG(1) << __func__ << " eglDestroySyncKHR failed.";
        success = false;
      }
    }

    DVLOG(1) << "DestroyOutputBuffers(): dismissing PictureBuffer id="
             << output_record.picture_id;
    child_message_loop_proxy_->PostTask(
        FROM_HERE,
        base::Bind(
            &Client::DismissPictureBuffer, client_, output_record.picture_id));
  }

  struct v4l2_requestbuffers reqbufs;
  memset(&reqbufs, 0, sizeof(reqbufs));
  reqbufs.count = 0;
  reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
  reqbufs.memory = V4L2_MEMORY_MMAP;
  if (device_->Ioctl(VIDIOC_REQBUFS, &reqbufs) != 0) {
    DPLOG(ERROR) << "DestroyOutputBuffers() ioctl() failed: VIDIOC_REQBUFS";
    success = false;
  }

  output_buffer_map_.clear();
  while (!free_output_buffers_.empty())
    free_output_buffers_.pop();

  return success;
}

void V4L2VideoDecodeAccelerator::ResolutionChangeDestroyBuffers() {
  DCHECK(child_message_loop_proxy_->BelongsToCurrentThread());
  DVLOG(3) << "ResolutionChangeDestroyBuffers()";

  if (!DestroyOutputBuffers()) {
    DLOG(FATAL) << __func__ << " Failed destroying output buffers.";
    NOTIFY_ERROR(PLATFORM_FAILURE);
    return;
  }

  // Finish resolution change on decoder thread.
  decoder_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
      &V4L2VideoDecodeAccelerator::FinishResolutionChange,
      base::Unretained(this)));
}

void V4L2VideoDecodeAccelerator::SendPictureReady() {
  DVLOG(3) << "SendPictureReady()";
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  bool resetting_or_flushing =
      (decoder_state_ == kResetting || decoder_flushing_);
  while (pending_picture_ready_.size() > 0) {
    bool cleared = pending_picture_ready_.front().cleared;
    const media::Picture& picture = pending_picture_ready_.front().picture;
    if (cleared && picture_clearing_count_ == 0) {
      // This picture is cleared. Post it to IO thread to reduce latency. This
      // should be the case after all pictures are cleared at the beginning.
      io_message_loop_proxy_->PostTask(
          FROM_HERE, base::Bind(&Client::PictureReady, io_client_, picture));
      pending_picture_ready_.pop();
    } else if (!cleared || resetting_or_flushing) {
      DVLOG(3) << "SendPictureReady()"
               << ". cleared=" << pending_picture_ready_.front().cleared
               << ", decoder_state_=" << decoder_state_
               << ", decoder_flushing_=" << decoder_flushing_
               << ", picture_clearing_count_=" << picture_clearing_count_;
      // If the picture is not cleared, post it to the child thread because it
      // has to be cleared in the child thread. A picture only needs to be
      // cleared once. If the decoder is resetting or flushing, send all
      // pictures to ensure PictureReady arrive before reset or flush done.
      child_message_loop_proxy_->PostTaskAndReply(
          FROM_HERE,
          base::Bind(&Client::PictureReady, client_, picture),
          // Unretained is safe. If Client::PictureReady gets to run, |this| is
          // alive. Destroy() will wait the decode thread to finish.
          base::Bind(&V4L2VideoDecodeAccelerator::PictureCleared,
                     base::Unretained(this)));
      picture_clearing_count_++;
      pending_picture_ready_.pop();
    } else {
      // This picture is cleared. But some pictures are about to be cleared on
      // the child thread. To preserve the order, do not send this until those
      // pictures are cleared.
      break;
    }
  }
}

void V4L2VideoDecodeAccelerator::PictureCleared() {
  DVLOG(3) << "PictureCleared(). clearing count=" << picture_clearing_count_;
  DCHECK_EQ(decoder_thread_.message_loop(), base::MessageLoop::current());
  DCHECK_GT(picture_clearing_count_, 0);
  picture_clearing_count_--;
  SendPictureReady();
}

}  // namespace content